svelte-on-rails 5.1.0 → 5.3.0

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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +106 -60
  3. data/lib/generators/svelte_on_rails/install/install_generator.rb +7 -3
  4. data/lib/svelte-on-rails.rb +2 -0
  5. data/lib/svelte_on_rails/action_cable.rb +49 -20
  6. data/lib/svelte_on_rails/active_record_extensions.rb +19 -0
  7. data/lib/svelte_on_rails/installer/hello_world.rb +1 -1
  8. data/lib/svelte_on_rails/lib/utils.rb +147 -0
  9. data/lib/svelte_on_rails/lib/view_helper_support.rb +3 -30
  10. data/lib/svelte_on_rails/railtie.rb +6 -0
  11. data/lib/svelte_on_rails/turbo_stream.rb +43 -3
  12. data/lib/tasks/svelte_on_rails_tasks.rake +2 -2
  13. data/templates/all_features_test/app/controllers/svelte_on_rails_hello_world_controller.rb +66 -0
  14. data/templates/all_features_test/app/frontend/initializers/actionCable.js +17 -0
  15. data/templates/all_features_test/app/frontend/javascript/components/ReceiveFromChannel.svelte +70 -0
  16. data/templates/{rails_vite_hello_world → all_features_test}/app/views/svelte_on_rails_hello_world/_nav.html.erb +1 -1
  17. data/templates/config_base/config/svelte_on_rails.yml +4 -0
  18. metadata +24 -22
  19. data/templates/rails_vite_hello_world/app/controllers/svelte_on_rails_hello_world_controller.rb +0 -35
  20. data/templates/rails_vite_hello_world/app/frontend/javascript/components/ReceiveFromChannel.svelte +0 -57
  21. /data/templates/{rails_vite_hello_world → all_features_test}/app/channels/svelte_on_rails_channel.rb +0 -0
  22. /data/templates/{rails_vite_hello_world → all_features_test}/app/frontend/images/svelte-on-rails-hello-world-england.png +0 -0
  23. /data/templates/{rails_vite_hello_world → all_features_test}/app/frontend/images/svelte-on-rails-hello-world-face-smile-wink.svg +0 -0
  24. /data/templates/{rails_vite_hello_world → all_features_test}/app/frontend/images/svelte-on-rails-hello-world-switzerland.jpg +0 -0
  25. /data/templates/{rails_vite_hello_world → all_features_test}/app/frontend/javascript/components/JavascriptImport.svelte +0 -0
  26. /data/templates/{rails_vite_hello_world → all_features_test}/app/frontend/javascript/components/JpgImport.svelte +0 -0
  27. /data/templates/{rails_vite_hello_world → all_features_test}/app/frontend/javascript/components/ParentWithChild.svelte +0 -0
  28. /data/templates/{rails_vite_hello_world → all_features_test}/app/frontend/javascript/components/PngImport.svelte +0 -0
  29. /data/templates/{rails_vite_hello_world → all_features_test}/app/frontend/javascript/components/SvelteOnRailsHelloWorld.svelte +0 -0
  30. /data/templates/{rails_vite_hello_world → all_features_test}/app/frontend/javascript/components/SvgRawImport.svelte +0 -0
  31. /data/templates/{rails_vite_hello_world → all_features_test}/app/frontend/javascript/components/sub/NestedComponent.svelte +0 -0
  32. /data/templates/{rails_vite_hello_world → all_features_test}/app/frontend/javascript/nestedJavascript.js +0 -0
  33. /data/templates/{rails_vite_hello_world → all_features_test}/app/frontend/javascript/nestedJavascriptToggled.js +0 -0
  34. /data/templates/{rails_vite_hello_world → all_features_test}/app/views/svelte_on_rails_hello_world/_styles.html.erb +0 -0
  35. /data/templates/{rails_vite_hello_world → all_features_test}/app/views/svelte_on_rails_hello_world/backend_frontend_rendered.html.erb +0 -0
  36. /data/templates/{rails_vite_hello_world → all_features_test}/app/views/svelte_on_rails_hello_world/index.html.erb +0 -0
  37. /data/templates/{rails_vite_hello_world → all_features_test}/app/views/svelte_on_rails_hello_world/ssr_auto_rendered.html.erb +0 -0
  38. /data/templates/{rails_vite_hello_world/app/views/svelte_on_rails_hello_world/turbo_streams_channel.html.erb → all_features_test/app/views/svelte_on_rails_hello_world/web_socket.html.erb} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0e2782073f696f72736004ae21d57a71e4429fb878f915954e36628e16904de9
4
- data.tar.gz: 2d041a60e7908ce20ab64f490ccebe3d3c673882986c83a64981aaf1e9931768
3
+ metadata.gz: 8f91cbdd2375be3978749859e8bb36b16fbba13fa1e9ac494b9b5e731bc06f91
4
+ data.tar.gz: f0164b5a93bfb62b2abeedbaebc85064253ad6e4500d9d94a0cfda570416e794
5
5
  SHA512:
6
- metadata.gz: 5d1fef990a79dfa782b0101f33ed64f3631d126ea6ef16dfde8d21d240653d8d938a87689955af288b862362216fbb760db61e79d709c512b242d0f86ad823d1
7
- data.tar.gz: 430b9cf7ab45fb7e0ab629b4de95bd262e8b02bd68a58793dcd614832dfd2bb04d63f52d20ddc6639dc951d8ae132188dee755dadab1042f98a0268dcf4bab63
6
+ metadata.gz: f490b501d07cfe64636b73e45ba2b42d30cfc2fc33168488635565fee4a1d3def9e31b5097cbba82aa7ebfad92e08b6a6be30afa53385dbb206e616d6a72352e
7
+ data.tar.gz: 94e88d6876d9d434e504899f47f5d71ba2d3129a3a55a94992ce6a573d9e78ab305e306f7c3c2576c0135b3aeb03c5578fec74175eb2fc18d89151bac059d664
data/README.md CHANGED
@@ -34,11 +34,14 @@ vision while aligning seamlessly with Rails’ full-stack philosophy.
34
34
  - Svelte eliminates redundant HTML state logic
35
35
  - Consolidates component logic into a single file
36
36
  - Offloads rendering of certain HTML components to frontend JavaScript, reducing server load
37
- - **Compared to React**
38
- - Watch Rich Harris’ [Rethinking Reactivity](https://svelte.dev/blog/svelte-3-rethinking-reactivity) (3:50–6:40) for a compelling comparison
39
- - No shadow DOM or performance-heavy packages (e.g., useCallback)
40
- - Leaner, slimmer packages
41
- - Easier to learn and faster performance
37
+ - **Compared to React/Vue**
38
+ - Svelte eliminates the virtual DOM, resulting in leaner, more compact packages and faster performance
39
+ - Watch Rich Harris’ [Rethinking Reactivity](https://svelte.dev/blog/svelte-3-rethinking-reactivity) (3:50–6:40) for a compelling comparison with react
40
+ - Easier to learn
41
+ - React and Vue having a vaster Community, bigger Ecosystem
42
+ - But, for integration, like here, this is not importand
43
+ - Svelte is sufficiently established
44
+ - Svelte is younger and growing
42
45
 
43
46
  Svelte empowers Rails’ full-stack vision with modern, streamlined frontend integration.
44
47
 
@@ -307,6 +310,46 @@ Consider setting `ssr: false` for testing only for performance reasons.
307
310
  Yo can override this on a attribute on the view-helper for specific components.
308
311
  But, in normal cases it should not be neccessary testing ssr explicitly.
309
312
 
313
+ ## ActiveRecord helpers
314
+
315
+ Adds the `#svelte_attributes` helper to your models, example:
316
+
317
+ ```ruby
318
+ @book.svelte_attributes(
319
+ :name,
320
+ :calculation_method,
321
+ author: [:name]
322
+ )
323
+ ```
324
+
325
+ would result in something like this:
326
+
327
+ ```ruby
328
+ {
329
+ "values" => {
330
+ "name" => "Learning Ruby",
331
+ "calculation_method": "any-result",
332
+ "author" => {
333
+ "name" => "Michael Hartl"
334
+ },
335
+ },
336
+ "book_labels" => {
337
+ "name" => "Name", # translated by human_attribute_name..
338
+ "calculation_method" => "Calculation method",
339
+ "author" => "Author"
340
+ },
341
+ "author_labels": {
342
+ "name" => "Name"
343
+ }
344
+ }
345
+ ```
346
+
347
+ This should ease transferring data you need within the component mostly.
348
+
349
+ If an optional `belongs_to` association is empty, the labels are still calculated.
350
+
351
+ **Caching:** The component's attributes are used to generate a checksum, which serves as the cache key for efficient storage and retrieval.
352
+
310
353
  ## Caching
311
354
 
312
355
  Caching only is relevant for `ssr`
@@ -365,7 +408,17 @@ turbo_stream:
365
408
  channel: 'public'
366
409
  ```
367
410
 
368
- ## ActionCable!
411
+ ## ActionCable vs TurboStream
412
+
413
+ - **ActionCable**
414
+ - Cleaner setup no html needed
415
+ - Seams to initialize securer, especially for testings (not: production)
416
+ - **TurboStream**
417
+ - Secured Streams by html-tag `signed-stream-name` if you want to send confidential data
418
+ over streams or have different channels for each user privileges
419
+ - Has [Compatibility issue with UJS](https://github.com/hotwired/turbo-rails?tab=readme-ov-file#compatibility-with-rails-ujs)
420
+
421
+ ## SvelteOnRails::ActionCable
369
422
 
370
423
  ActionCable is the more basic library behind `TurboStream` and it is a second Option to call Javascript Actions from the server.
371
424
 
@@ -395,7 +448,8 @@ Add to `application.js`
395
448
 
396
449
  ```javascript
397
450
  import { createConsumer } from "@rails/actioncable"
398
- import {dispatchSvelteStreamEvent, actionCableDebugLog} from '@csedl/svelte-on-rails'
451
+ import { SvelteOnRails, dispatchSvelteStreamEvent, actionCableDebugLog } from '@csedl/svelte-on-rails'
452
+ SvelteOnRails.debug = true
399
453
 
400
454
  const consumer = createConsumer()
401
455
 
@@ -413,9 +467,7 @@ consumer.subscriptions.create("SvelteOnRailsChannel", {
413
467
  })
414
468
  ```
415
469
 
416
- By setting `debug = true` [node package](https://www.npmjs.com/package/@csedl/svelte-on-rails) you see what it is doing.
417
-
418
- **WhatdispatchSvelteStreamEvent does**
470
+ **What dispatchSvelteStreamEvent does**
419
471
 
420
472
  - Without the attribute `component` given,
421
473
  it searches for all elements with the class `svelte-component` and fires the event `channel-action`
@@ -424,18 +476,43 @@ By setting `debug = true` [node package](https://www.npmjs.com/package/@csedl/sv
424
476
 
425
477
  **Usage**
426
478
 
479
+ `app/frontend/javascript/components/folder/MyComponent.svelte`
480
+
481
+ ```sveltehtml
482
+ <script>
483
+ import {addComponentStreamListener} from '@csedl/svelte-on-rails/src/componentStreamListener'
484
+
485
+ function handleCableEvent(event) {
486
+ console.log('Event received by Turbo Stream', event.detail)
487
+ }
488
+ </script>
489
+ <!--on ANY element:-->
490
+ <h1 use:addComponentStreamListener={handleCableEvent}>Test TurboStreams Channel</h1>
491
+ ```
492
+
493
+ The `addComponentStreamListener` adds the eventListener `stream-action` on the wrapping Element.
494
+ The «wrapping Element» is the Element from the view helper `svelte_component` with the class `svelte-component`.
495
+
427
496
  Now you can dispatch events on the component by:
428
497
 
429
498
  ```ruby
430
- ActionCable.server.broadcast("svelte_on_rails_channel",
431
- {
432
- eventDetail: { message: "Sent by ActionCable: äöü🤣🌴🌍漢字" },
433
- component: '/javascript/components/ReceiveFromChannel',
434
- }
499
+ SvelteOnRails::ActionCable.dispatch(
500
+ 'folder/MyComponent',
501
+ { message: "greetings from Server: äöü🤣🌴🌍漢字" }
435
502
  )
436
503
  ```
437
504
 
438
- # Turbo::StreamsChannel.send!
505
+ And you will find the object, with the message key on the browser logs.
506
+
507
+ Without any arguments, just by `SvelteOnRails::ActionCable.dispatch` it would fire the `stream-action` event on all components.
508
+
509
+ The **#dispatch_by_selector** does not go over the component, it searches for any matching selector just
510
+ on the whole `document` and fires the given event there.
511
+
512
+ # SvelteOnRails::TurboStream
513
+
514
+ Turbo Stream makes more sense when you think of sending confidential data to the components
515
+ or you want to separate to channels based on user groups, for example.
439
516
 
440
517
  Few setup is needed for that:
441
518
 
@@ -449,15 +526,15 @@ rails turbo:install
449
526
  ```
450
527
 
451
528
  make sure that `import "@hotwired/turbo-rails"` is on your application.js and set the view helper
452
- `<%= turbo_stream_from 'public' %>` to your view.
529
+ `<%= turbo_stream_from 'authenticated' if current_user %>` to your view.
453
530
 
454
- If a channel (e.g.: `public`) is active and you have an HTML element with a HTML-ID (e.g.: `svelte-on-rails-stream-actions-box`),
531
+ If a channel (e.g.: `authenticated`) is active and you have an HTML element with a HTML-ID (e.g.: `svelte-on-rails-stream-actions-box`),
455
532
  you can test it by:
456
533
 
457
534
  ```ruby
458
535
  Turbo::StreamsChannel.send(
459
536
  "broadcast_append_to",
460
- 'public',
537
+ 'authenticated',
461
538
  target: 'svelte-on-rails-stream-actions-box',
462
539
  content: "<div>Turbo-Streams are working!</div>"
463
540
  )
@@ -467,27 +544,13 @@ When this works you are good to go.
467
544
 
468
545
  **Minimal Usage Example**
469
546
 
470
- ```sveltehtml
471
- <script>
472
- import {addComponentStreamListener} from '@csedl/svelte-on-rails/src/componentStreamListener'
473
-
474
- function handleCableEvent() {
475
- console.log('Event received by Turbo Stream')
476
- }
477
- </script>
478
- <!--on any element:-->
479
- <h1 use:addComponentStreamListener={handleCableEvent}>Test TurboStreams Channel</h1>
480
- ```
481
-
482
547
  And call this by:
483
548
 
484
549
  ```ruby
485
- SvelteOnRails::TurboStream.new.dispatch_event
550
+ SvelteOnRails::TurboStream.dispatch
486
551
  ```
487
552
  **What it does**
488
553
 
489
- - The `addComponentStreamListener` adds the eventListener `stream-action` on the wrapping Element
490
- - The «wrapping Element» is the Element from the view helper `svelte_component` with the class `svelte-component`.
491
554
  - A Stimulus controller is pushed to all subscribers of the configured channel.
492
555
  - You can override the channel name by passing `channel` to the method.
493
556
 
@@ -510,7 +573,7 @@ within the svelte component.
510
573
  Then, call it by:
511
574
 
512
575
  ```ruby
513
- SvelteOnRails::TurboStream.new.dispatch_event(
576
+ SvelteOnRails::TurboStream.dispatch(
514
577
  channel: 'my-custom-stream',
515
578
  component: '/javascript/components/TurboStreamsChannel',
516
579
  selector: '.counter-button',
@@ -519,7 +582,8 @@ Then, call it by:
519
582
  )
520
583
  ```
521
584
 
522
-
585
+ The **#dispatch_by_selector** does not go over the component, it searches for any matching selector just
586
+ on the whole `document` and fires the given event there.
523
587
 
524
588
  ## More rake tasks
525
589
 
@@ -575,30 +639,12 @@ This will cause the installer, to install the npm package from a local path inst
575
639
  Then run the tests and start contributing.
576
640
 
577
641
  **RUN THE FIRST TEST (!)**: Testing is complex here because of the design based on the installer test.
578
- On Problems, i always run the `Installer > destroy and create rails app > FIRST TEST > [...] check if javascript works`. When this passes,
579
- all the others passing mostly.
580
-
581
- Tests are based on the included templates, like the `hello world template` and on the installer.
582
-
583
- Here I learned how to write tests! Testing installers is heavy, you will read about it in the
584
- test_helpers. But I have done my best to give all the code a clear structure.
585
- I hope you like it, and improvements are welcome.
586
-
587
- **Installer tests** starting with completely destroy the rails app within the `generated_test_app_path`,
588
- generating a new rails app and running the installer and test by `playwright` if the components are working.
589
-
590
- **component tests** only checking if a rails server is alive, and if not, install and run a rails app.
591
- For this is the testing helper `start_rails_server_unless_ping`. This step may only be slow on the
592
- first run, then it is fast. And on every repeating the test it always overwrites the components
593
- with the components from the template by the testing helper `install_hello_world(
594
- ['rails_vite_hello_world'],
595
- app_root: generated_rails_app_root,
596
- force: true,
597
- silent: true
598
- )`. At the end of the test it leaves the rails server running.
599
-
600
- On that way a developer can just edit the templates and run a test and see always the refreshed
601
- content on the browser and on the app within the `generated_test_app_path`.
642
+ On Problems, i always run the `Installer > destroy and create rails app > FIRST TEST > [...] check if javascript works`.
643
+ If there are problems, open the generated app on a IDE and check errors there.
644
+
645
+ When this passes, all the others passing mostly.
646
+
647
+ At the end of the most tests it leaves the rails server running, so that you can see the result on `localhost:3000`.
602
648
 
603
649
  NOTE: Theese tests are dependend on your environment, including the running ruby version!
604
650
  I am working on rvm. If you work on a different environment, some (not many) changes may be necessary.
@@ -129,11 +129,15 @@ module SvelteOnRails
129
129
  hw_i = SvelteOnRails::Installer::HelloWorld
130
130
 
131
131
  utils_i = SvelteOnRails::Installer::Utils
132
- utils_i.add_route(" get \"svelte_on_rails_hello_world/turbo_streams_channel\"", app_root: Rails.root)
133
- utils_i.add_route(" get \"svelte_on_rails_hello_world/turbo_stream_action\"", app_root: Rails.root)
132
+ utils_i.add_route(" get \"svelte_on_rails_hello_world/web_socket\"", app_root: Rails.root)
133
+ utils_i.add_route(" get \"svelte_on_rails_hello_world/web_socket_action\"", app_root: Rails.root)
134
134
  npm_i = SvelteOnRails::Installer::Npm
135
135
  npm_i.install_or_update_package('axios')
136
- @hello_world_path = hw_i.install_hello_world(['rails_vite_hello_world'], app_root: nil, force: true, silent: true)
136
+ npm_i.install_or_update_package('@rails/actioncable')
137
+ js_i = SvelteOnRails::Installer::Javascript
138
+ init_stat = '../initializers/actionCable.js'
139
+ js_i.append_import_statement(application_js_path, init_stat, "import '#{init_stat}';")
140
+ @hello_world_path = hw_i.install_hello_world(['all_features_test'], app_root: nil, force: true, silent: true)
137
141
  end
138
142
 
139
143
  def haml_and_convert
@@ -2,6 +2,8 @@ require "svelte_on_rails/configuration"
2
2
  require "svelte_on_rails/view_helpers"
3
3
  require "svelte_on_rails/turbo_stream"
4
4
  require "svelte_on_rails/action_cable"
5
+ require "svelte_on_rails/active_record_extensions"
6
+
5
7
  require "svelte_on_rails/railtie" if defined?(Rails)
6
8
 
7
9
  require "svelte_on_rails/renderer/renderer"
@@ -1,6 +1,16 @@
1
1
  module SvelteOnRails
2
2
  class ActionCable
3
- def dispatch_event(event: 'stream-action', event_detail: nil, selector: nil, component: nil, channel: "svelte_on_rails_channel")
3
+ def self.dispatch(component = nil, event_detail = nil, event: 'stream-action', selector: nil, channel: nil)
4
+
5
+ utils = SvelteOnRails::Lib::Utils
6
+ conf = SvelteOnRails::Configuration.instance
7
+
8
+ _comp = if component
9
+ utils.validate_filename(component)
10
+ "/#{conf.components_folder + component}"
11
+ end
12
+
13
+ _channel = require_channel(svelte_on_rails_configs['channel'] || channel)
4
14
 
5
15
  if event != 'stream-action' && !selector
6
16
  raise "Another event name than the default one is only possible together with a selector"
@@ -8,35 +18,54 @@ module SvelteOnRails
8
18
 
9
19
  args = {
10
20
  eventDetail: event_detail,
11
- component: component,
21
+ component: _comp,
12
22
  event: event,
13
23
  selector: selector
14
24
  }
15
25
 
16
- #args_enc = Base64.strict_encode64(args.to_json)
26
+ ::ActionCable.server.broadcast(
27
+ _channel,
28
+ args
29
+ )
30
+
31
+ end
32
+
33
+ def self.dispatch_by_selector(selector, event_detail = nil, event: 'stream-action', channel: nil)
17
34
 
18
- ActionCable.server.broadcast(channel, args)
35
+ _channel = require_channel(svelte_on_rails_configs['channel'] || channel)
36
+
37
+ if event != 'stream-action' && !selector
38
+ raise "Another event name than the default one is only possible together with a selector"
39
+ end
40
+
41
+ args = {
42
+ eventDetail: event_detail,
43
+ component: '/false/',
44
+ event: event,
45
+ selector: selector
46
+ }
47
+
48
+ ::ActionCable.server.broadcast(
49
+ _channel,
50
+ args
51
+ )
19
52
 
20
53
  end
21
54
 
22
55
  private
23
56
 
24
- def configs
25
- @configs ||= begin
26
-
27
- conf = SvelteOnRails::Configuration.instance
28
- unless conf.configs[:turbo_stream]
29
- raise '[svelte-on-rails] missing configuration: :turbo_stream'
30
- end
31
- unless conf.configs[:turbo_stream]['target_html_id']
32
- raise '[svelte-on-rails] missing configuration: turbo_stream/target_html_id'
33
- end
34
- unless conf.configs[:turbo_stream]['channel']
35
- raise '[svelte-on-rails] missing configuration: turbo_stream/channel'
36
- end
37
-
38
- conf.configs[:turbo_stream]
39
- end
57
+ def self.svelte_on_rails_configs
58
+ cnf = SvelteOnRails::Configuration.instance.configs[:action_cable]
59
+ raise 'ActionCable not configured for SvelteOnRails' if cnf.nil?
60
+ cnf
40
61
  end
62
+
63
+ def self.require_channel(channel)
64
+ unless channel.present?
65
+ raise 'Missing attribute or configuration: action_cable/channel'
66
+ end
67
+ channel
68
+ end
69
+
41
70
  end
42
71
  end
@@ -0,0 +1,19 @@
1
+ # lib/svelte_on_rails/active_record_extensions.rb
2
+ module SvelteOnRails
3
+ module ActiveRecordExtensions
4
+ def self.included(base)
5
+ unless defined?(ActiveRecord::Base) && base.ancestors.include?(ActiveRecord::Base)
6
+ raise 'SvelteOnRails::ActiveRecordExtensions can only be included in ActiveRecord models'
7
+ end
8
+ end
9
+
10
+ # Returns a hash of attributes, methods, and associations formatted for Svelte components
11
+ def svelte_attributes(*attributes)
12
+ @svelte_attributes ||= begin
13
+ utils = SvelteOnRails::Lib::Utils
14
+ utils.svelte_attributes(self, attributes)
15
+ end
16
+ end
17
+
18
+ end
19
+ end
@@ -38,7 +38,7 @@ module SvelteOnRails
38
38
  end
39
39
 
40
40
 
41
- def self.remove_hello_world(templates = ['rails_vite_hello_world'], app_root: nil, ask: true)
41
+ def self.remove_hello_world(templates = ['all_features_test'], app_root: nil, ask: true)
42
42
 
43
43
  utils = SvelteOnRails::Installer::Utils
44
44
 
@@ -137,6 +137,153 @@ module SvelteOnRails
137
137
  Rails.logger.warn(" => #{line}")
138
138
  end
139
139
  end
140
+
141
+ def self.validate_filename(filename)
142
+
143
+ conf = SvelteOnRails::Configuration.instance
144
+
145
+ file_path = conf.components_folder_full + "#{filename}.svelte"
146
+
147
+ unless File.exist?(file_path)
148
+
149
+ raise "svelte-on-rails gem, view helper #svelte_component\n\nFile not found:\n" +
150
+ "#{conf.components_folder_full + "#{filename}.svelte"}\n\n" +
151
+ "Your configurations are:\n\nfrontend_folder: «#{conf.frontend_folder}»\ncomponents_folder: «#{conf.components_folder}»\n.. and the filename attribute for the view helper: «#{filename}»\n"
152
+
153
+ end
154
+
155
+ if conf.watch_changes? && !file_exist_case_sensitive?(conf.components_folder_full, "#{filename}.svelte")
156
+
157
+ raise "svelte-on-rails gem, view helper #svelte_component\n\n" +
158
+ "File found but Upper and lower case letters are incorrect:\n" +
159
+ "(This check is only on development environments when watch_changes is true)\n\n" +
160
+ "#{conf.components_folder_full + "#{filename}.svelte"}\n\n" +
161
+ "Your configurations are:\nfrontend_folder: «#{conf.frontend_folder}»\ncomponents_folder: «#{conf.components_folder}»\n.. and the filename attribute for the view helper: «#{filename}»\n"
162
+
163
+ end
164
+ end
165
+
166
+ def self.svelte_attributes(record, attributes, labels = {}, call_stack: 0, offset: nil, limit: nil)
167
+
168
+ if record.respond_to?(:each)
169
+ unless record.is_a?(ActiveRecord::Relation) || record.is_a?(ActiveRecord::Associations::CollectionProxy)
170
+ raise 'record set must be a Article::ActiveRecord_Associations_CollectionProxy'
171
+ end
172
+
173
+ recs = (limit ? record.limit(limit) : record)
174
+ recs2 = (offset ? recs.offset(limit) : recs)
175
+
176
+ values = recs2.map do |rec|
177
+ svelte_attributes(rec, attributes, labels, call_stack: call_stack + 1)
178
+ end
179
+
180
+ elsif record.is_a?(Class)
181
+
182
+ # this is the case if a belongs_to association is empty; In that case we pass the class itself and extract only the labels
183
+ raise 'limit and offset are supported only for iterable objects' if limit || offset
184
+ attributes.each do |attr|
185
+ unless attr.respond_to?(:each)
186
+ labels["#{record.to_s.underscore}_labels"] ||= {}
187
+ labels["#{record.to_s.underscore}_labels"][attr.to_s] ||= record.human_attribute_name(attr)
188
+ end
189
+ end
190
+ values = {}
191
+
192
+ else
193
+
194
+ # we have a single record
195
+
196
+ raise 'limit and offset are supported only for iterable objects' if limit || offset
197
+ values = {}
198
+
199
+ table_name = record.class.to_s.underscore
200
+
201
+ attributes.each do |attr|
202
+
203
+ if attr.is_a? Hash
204
+
205
+ # we have associations
206
+ attr.each do |key, value|
207
+ _key = key.to_s
208
+
209
+ # skip and fetch offset and limit
210
+ if key.to_s.match(/_limit$/) && !record.respond_to?(key)
211
+ raise 'Invalid attribute' unless record.respond_to?(key.to_s[0..-7])
212
+ next
213
+ end
214
+ if key.to_s.match(/_offset$/) && !record.respond_to?(key)
215
+ raise 'Invalid attribute' unless record.respond_to?(key.to_s[0..-8])
216
+ next
217
+ end
218
+ offs, lim = svelte_attribute_extract_limit(record, attributes, _key)
219
+
220
+ labels["#{table_name}_labels"] ||= {}
221
+ labels["#{table_name}_labels"][_key] ||= record.class.human_attribute_name(_key)
222
+
223
+ # values
224
+
225
+ content = record.send(_key)
226
+ reflect = record.class.reflect_on_association(_key)
227
+ if reflect
228
+ if content.respond_to?(:each)
229
+ values[_key] = svelte_attributes(
230
+ content, value, labels,
231
+ call_stack: call_stack + 1,
232
+ offset: offs,
233
+ limit: lim
234
+ )
235
+ else
236
+ values[_key] = svelte_attributes(
237
+ content || reflect.active_record, # if no record, we extract only the labels
238
+ value, labels,
239
+ call_stack: call_stack + 1,
240
+ offset: offs,
241
+ limit: lim
242
+ )
243
+ end
244
+ else
245
+ raise "invalid association: #{_key}"
246
+ end
247
+ end
248
+
249
+ else
250
+ # we have attributes
251
+ raise 'Invalid attribute' unless [Symbol, String].include?(attr.class)
252
+ _key = attr.to_s
253
+
254
+ labels["#{table_name}_labels"] ||= {}
255
+ labels["#{table_name}_labels"][_key] ||= record.class.human_attribute_name(_key)
256
+
257
+ values[_key] = record.send(_key)
258
+ end
259
+
260
+ end
261
+ end
262
+
263
+ if call_stack >= 1
264
+ values
265
+ else
266
+ { 'values' => values }.merge(labels)
267
+ end
268
+
269
+ end
270
+
271
+ private
272
+
273
+ def self.svelte_attribute_extract_limit(record, attributes, key)
274
+
275
+ hash_args = attributes.grep(Hash).first.with_indifferent_access # multiple arrays is not possible
276
+
277
+ offset = if hash_args["#{key}_offset"] && !record.respond_to?("#{key}_offset")
278
+ hash_args["#{key}_offset"]
279
+ end
280
+
281
+ limit = if hash_args["#{key}_limit"] && !record.respond_to?("#{key}_limit")
282
+ hash_args["#{key}_limit"]
283
+ end
284
+
285
+ [offset, limit]
286
+ end
140
287
  end
141
288
  end
142
289
  end
@@ -8,9 +8,11 @@ module SvelteOnRails
8
8
  def initialize(file, args, request, caching = false)
9
9
 
10
10
  @start_time = Time.now
11
+ @conf = SvelteOnRails::Configuration.instance
12
+ utils = SvelteOnRails::Lib::Utils
13
+ utils.validate_filename(file) if @conf.watch_changes?
11
14
  @filename = (file.match(/\.svelte$/) ? file[0..-8] : file)
12
15
  @args_checksum = Zlib.crc32(args.to_json).to_s(36)
13
- @conf = SvelteOnRails::Configuration.instance
14
16
  @helper_options, @html_options, @svelte_props = split_props(
15
17
  args,
16
18
  %i[class id style],
@@ -18,7 +20,6 @@ module SvelteOnRails
18
20
  )
19
21
  @request = request
20
22
  @ssr = determine_ssr
21
- validate_file if @conf.watch_changes?
22
23
 
23
24
  # precompile
24
25
 
@@ -64,24 +65,6 @@ module SvelteOnRails
64
65
  @ssr
65
66
  end
66
67
 
67
- def file_not_found_message
68
- ff = conf.frontend_folder
69
- cf = conf.components_folder
70
- "svelte-on-rails gem, view helper #svelte_component\n\nFile not found:\n" +
71
- "#{conf.components_folder_full + "#{filename}.svelte"}\n\n" +
72
- "Your configurations are:\n\nfrontend_folder: «#{ff}»\ncomponents_folder: «#{cf}»\n.. and the filename attribute for the view helper: «#{filename}»\n"
73
- end
74
-
75
- def file_case_sensitive_message
76
- ff = conf.frontend_folder
77
- cf = conf.components_folder
78
- "svelte-on-rails gem, view helper #svelte_component\n\n" +
79
- "File found but Upper and lower case letters are incorrect:\n" +
80
- "(This check is only on development environments when watch_changes is true)\n\n" +
81
- "#{conf.components_folder_full + "#{filename}.svelte"}\n\n" +
82
- "Your configurations are:\nfrontend_folder: «#{ff}»\ncomponents_folder: «#{cf}»\n.. and the filename attribute for the view helper: «#{filename}»\n"
83
- end
84
-
85
68
  def log_rendering(message)
86
69
 
87
70
  Rails.logger.info " #{message} (#{elapsed_milliseconds}ms)"
@@ -119,16 +102,6 @@ module SvelteOnRails
119
102
 
120
103
  private
121
104
 
122
- def validate_file
123
- file_path = conf.components_folder_full + "#{filename}.svelte"
124
- unless File.exist?(file_path)
125
- raise file_not_found_message
126
- end
127
- if conf.watch_changes? && !SvelteOnRails::Lib::Utils.file_exist_case_sensitive?(conf.components_folder_full, "#{filename}.svelte")
128
- raise file_case_sensitive_message
129
- end
130
- end
131
-
132
105
  def determine_ssr
133
106
  _ssr = if @helper_options.key?(:ssr)
134
107
  if @conf.watch_changes?
@@ -13,6 +13,12 @@ module SvelteOnRails
13
13
  SvelteOnRails::Configuration.instance
14
14
  end
15
15
 
16
+ initializer 'svelte_on_rails.active_record_extensions' do
17
+ ActiveSupport.on_load(:active_record) do
18
+ include SvelteOnRails::ActiveRecordExtensions
19
+ end
20
+ end
21
+
16
22
  rake_tasks do
17
23
  load File.expand_path("../../tasks/svelte_on_rails_tasks.rake", __FILE__)
18
24
  end
@@ -1,6 +1,14 @@
1
1
  module SvelteOnRails
2
2
  class TurboStream
3
- def dispatch_event(event: 'stream-action', event_detail: nil, selector: nil, component: nil, channel: nil)
3
+ def self.dispatch(component = nil, event_detail = nil, event: 'stream-action', selector: nil, channel: nil)
4
+
5
+ utils = SvelteOnRails::Lib::Utils
6
+ conf = SvelteOnRails::Configuration.instance
7
+
8
+ _comp = if component
9
+ utils.validate_filename(component)
10
+ "/#{conf.components_folder + component}"
11
+ end
4
12
 
5
13
  if event != 'stream-action' && !selector
6
14
  raise "Another event name than the default one is only possible together with a selector"
@@ -8,7 +16,7 @@ module SvelteOnRails
8
16
 
9
17
  args = {
10
18
  eventDetail: event_detail,
11
- component: component,
19
+ component: _comp,
12
20
  event: event,
13
21
  selector: selector
14
22
  }
@@ -24,9 +32,33 @@ module SvelteOnRails
24
32
 
25
33
  end
26
34
 
35
+ def self.dispatch_by_selector(selector, event_detail = nil, event: 'stream-action', channel: nil)
36
+
37
+ if event != 'stream-action' && !selector
38
+ raise "Another event name than the default one is only possible together with a selector"
39
+ end
40
+
41
+ args = {
42
+ eventDetail: event_detail,
43
+ component: '/false/',
44
+ event: event,
45
+ selector: selector
46
+ }
47
+
48
+ args_enc = Base64.strict_encode64(args.to_json)
49
+
50
+ Turbo::StreamsChannel.send(
51
+ "broadcast_append_to",
52
+ require_channel(channel || configs['channel']),
53
+ target: configs['target_html_id'],
54
+ content: "<div style=\"display: none;\" data-controller=\"svelte-on-rails-turbo-stream\" data-args=\"#{args_enc}\"></div>"
55
+ )
56
+
57
+ end
58
+
27
59
  private
28
60
 
29
- def configs
61
+ def self.configs
30
62
  @configs ||= begin
31
63
 
32
64
  conf = SvelteOnRails::Configuration.instance
@@ -43,5 +75,13 @@ module SvelteOnRails
43
75
  conf.configs[:turbo_stream]
44
76
  end
45
77
  end
78
+
79
+ def self.require_channel(channel)
80
+ unless channel.present?
81
+ raise 'Missing attribute or configuration: turbo_stream/channel'
82
+ end
83
+ channel
84
+ end
85
+
46
86
  end
47
87
  end
@@ -18,7 +18,7 @@ namespace :svelte_on_rails do
18
18
  task :remove_hello_world do
19
19
 
20
20
  hw_i = SvelteOnRails::Installer::HelloWorld
21
- hw_i.remove_hello_world(['rails_vite_hello_world'])
21
+ hw_i.remove_hello_world(['all_features_test'])
22
22
 
23
23
  end
24
24
 
@@ -27,7 +27,7 @@ namespace :svelte_on_rails do
27
27
 
28
28
  puts '-' * 80
29
29
  hw_i = SvelteOnRails::Installer::HelloWorld
30
- hello_world_path = hw_i.install_hello_world(['rails_vite_hello_world'])
30
+ hello_world_path = hw_i.install_hello_world(['all_features_test'])
31
31
  puts "You can now see the Hello World component on: #{hello_world_path}."
32
32
 
33
33
  end
@@ -0,0 +1,66 @@
1
+ class SvelteOnRailsHelloWorldController < ApplicationController
2
+
3
+ def index
4
+ end
5
+
6
+ def web_socket_action
7
+
8
+ # render plain: Article.first.svelte_attributes(:name) #(:name, :calc_method, children: [:name], children_limit: 2, parent: [:name]).to_json
9
+ #
10
+ # return
11
+
12
+ comp = 'ReceiveFromChannel'
13
+
14
+ case params['stream']
15
+
16
+ when 'action-cable-to-component'
17
+ SvelteOnRails::ActionCable.dispatch(
18
+ comp,
19
+ { message: "#{SecureRandom.hex(2)} Sent by <span class='transfer'>ActionCable</span>: äöü🤣🌴🌍漢字", class: 'action-cable-to-component' },
20
+ event: 'stream-action'
21
+ )
22
+
23
+ when 'action-cable-to-element'
24
+ SvelteOnRails::ActionCable.dispatch(
25
+ comp,
26
+ { message: "#{SecureRandom.hex(2)} <span class='transfer'>ActionCable to .my-custom-class / my-custom-event</span>", class: 'action-cable-to-element' },
27
+ selector: '.my-custom-class',
28
+ event: 'my-custom-event'
29
+ )
30
+
31
+ when 'action-cable-to-selector'
32
+ SvelteOnRails::ActionCable.dispatch_by_selector(
33
+ '.receive-by-selector',
34
+ { message: "Sent by ActionCable/Selector: äöü🤣🌴🌍漢字", class: 'action-cable-to-selector' }
35
+ )
36
+
37
+ when 'turbo-stream-to-all-components'
38
+ SvelteOnRails::TurboStream.dispatch(
39
+ nil,
40
+ { message: "Sent by TurboStream: äöü🤣🌴🌍漢字", class: 'turbo-stream-to-all-components' },
41
+ )
42
+
43
+ when 'turbo-stream-to-element'
44
+ SvelteOnRails::TurboStream.dispatch(
45
+ nil,
46
+ { message: "Sent by TurboStream: äöü🤣🌴🌍漢字", class: 'turbo-stream-to-element' },
47
+ selector: '.my-custom-class',
48
+ event: 'my-custom-event'
49
+ )
50
+
51
+ when 'turbo-stream-to-selector'
52
+ SvelteOnRails::TurboStream.dispatch_by_selector(
53
+ '.receive-by-selector',
54
+ { message: "Sent by TurboStream/Selector: äöü🤣🌴🌍漢字", class: 'turbo-stream-to-selector' }
55
+ )
56
+
57
+ else
58
+ raise 'Unknown stream'
59
+
60
+ end
61
+
62
+ render plain: "dispatched: #{params['stream']}"
63
+
64
+ end
65
+
66
+ end
@@ -0,0 +1,17 @@
1
+ import { createConsumer } from "@rails/actioncable"
2
+ import {dispatchSvelteStreamEvent, actionCableDebugLog} from '@csedl/svelte-on-rails'
3
+
4
+ const consumer = createConsumer()
5
+
6
+ consumer.subscriptions.create("SvelteOnRailsChannel", {
7
+ connected() {
8
+ actionCableDebugLog("Connected to SvelteOnRailsChannel")
9
+ },
10
+ disconnected() {
11
+ actionCableDebugLog("Disconnected from SvelteOnRailsChannel")
12
+ },
13
+ received(data) {
14
+ actionCableDebugLog("Received:", data)
15
+ dispatchSvelteStreamEvent(data)
16
+ }
17
+ })
@@ -0,0 +1,70 @@
1
+ <script lang="ts">
2
+ import axios from "axios";
3
+ import {addComponentStreamListener} from '@csedl/svelte-on-rails/src/componentStreamListener'
4
+
5
+ let results = $state([])
6
+
7
+ function callChannelAction(action) {
8
+ results = []
9
+ axios.get(`/svelte_on_rails_hello_world/web_socket_action?stream=${action}`)
10
+ .then(function (response) {
11
+ console.log(`server action called, status: ${response.status}`)
12
+ })
13
+ }
14
+
15
+ const handleCableEvent = (ev) => {
16
+ results.push(
17
+ {
18
+ text: `Message received from Server: ${JSON.stringify(ev.detail.message)}`,
19
+ class: ev.detail.class
20
+ }
21
+ )
22
+ };
23
+
24
+ function handleElementEvent(ev) {
25
+ results.push(
26
+ {
27
+ text: `Element triggered from Server: ${JSON.stringify(ev.detail.message)}`,
28
+ class: ev.detail.class
29
+ }
30
+ )
31
+ };
32
+
33
+ function handleStreamSelectorEvent(ev) {
34
+ results.push(
35
+ {
36
+ text: `Element triggered from Server: ${JSON.stringify(ev.detail.message)}`,
37
+ class: ev.detail.class
38
+ }
39
+ )
40
+ }
41
+
42
+ </script>
43
+
44
+ <h1 use:addComponentStreamListener={handleCableEvent}>Dispatch Actions from Server</h1>
45
+
46
+ <p>Actions that are triggered from the server, by TurboStream or ActionCable</p>
47
+
48
+ <h3>ActionCable</h3>
49
+ <p>Easier Technic, more basic</p>
50
+ <button class="action-cable-to-component" onclick="{() => callChannelAction('action-cable-to-component')}">to component</button>
51
+ <button class="action-cable-to-element" onclick="{() => callChannelAction('action-cable-to-element')}">to element</button>
52
+ <button class="action-cable-to-element" onclick="{() => callChannelAction('action-cable-to-selector')}">to selector</button>
53
+
54
+ <h3>TurboStream</h3>
55
+ <p>Securer Channels, for example if sending confidential data from the server to privileged user groups</p>
56
+ <button class="turbo-stream-to-all-components" onclick="{() => callChannelAction('turbo-stream-to-all-components')}">to all components</button>
57
+ <button class="turbo-stream-to-element" onclick="{() => callChannelAction('turbo-stream-to-element')}">to element</button>
58
+ <button class="turbo-stream-to-element" onclick="{() => callChannelAction('turbo-stream-to-selector')}">to selector</button>
59
+
60
+ <span class="my-custom-class" onmy-custom-event="{handleElementEvent}"></span>
61
+ <span class="receive-by-selector" onstream-action="{handleStreamSelectorEvent}"></span>
62
+
63
+ <h3>Results</h3>
64
+ <div class="results-box">
65
+ <ul class="results">
66
+ {#each results as result}
67
+ <li class="{result.class}">{@html result.text}</li>
68
+ {/each}
69
+ </ul>
70
+ </div>
@@ -5,7 +5,7 @@
5
5
  |
6
6
  <%= link_to 'SSR-Auto rendered (default)', '/svelte_on_rails_hello_world/ssr_auto_rendered' %>
7
7
  |
8
- <%= link_to 'Turbo Streams', '/svelte_on_rails_hello_world/turbo_streams_channel' %>
8
+ <%= link_to 'Turbo Streams', '/svelte_on_rails_hello_world/web_socket' %>
9
9
  </div>
10
10
 
11
11
  <% turbo_id = request.headers['X-Turbo-Request-ID'] %>
@@ -27,6 +27,10 @@ turbo_stream:
27
27
  # html-id of any element that must exist for being able to receive actions (turbo streams can only work this way)
28
28
  channel: 'public'
29
29
 
30
+ action_cable:
31
+ # if you want to use it
32
+ channel: "svelte_on_rails_channel"
33
+
30
34
  development:
31
35
  watch_changes: true
32
36
  # Check on every request if any file within the svelte components folder have changed, for recompiling
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: svelte-on-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.1.0
4
+ version: 5.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christian Sedlmair
@@ -32,6 +32,7 @@ files:
32
32
  - lib/generators/svelte_on_rails/install/install_generator.rb
33
33
  - lib/svelte-on-rails.rb
34
34
  - lib/svelte_on_rails/action_cable.rb
35
+ - lib/svelte_on_rails/active_record_extensions.rb
35
36
  - lib/svelte_on_rails/configuration.rb
36
37
  - lib/svelte_on_rails/installer/gem_utils.rb
37
38
  - lib/svelte_on_rails/installer/haml.rb
@@ -51,30 +52,31 @@ files:
51
52
  - lib/svelte_on_rails/turbo_stream.rb
52
53
  - lib/svelte_on_rails/view_helpers.rb
53
54
  - lib/tasks/svelte_on_rails_tasks.rake
55
+ - templates/all_features_test/app/channels/svelte_on_rails_channel.rb
56
+ - templates/all_features_test/app/controllers/svelte_on_rails_hello_world_controller.rb
57
+ - templates/all_features_test/app/frontend/images/svelte-on-rails-hello-world-england.png
58
+ - templates/all_features_test/app/frontend/images/svelte-on-rails-hello-world-face-smile-wink.svg
59
+ - templates/all_features_test/app/frontend/images/svelte-on-rails-hello-world-switzerland.jpg
60
+ - templates/all_features_test/app/frontend/initializers/actionCable.js
61
+ - templates/all_features_test/app/frontend/javascript/components/JavascriptImport.svelte
62
+ - templates/all_features_test/app/frontend/javascript/components/JpgImport.svelte
63
+ - templates/all_features_test/app/frontend/javascript/components/ParentWithChild.svelte
64
+ - templates/all_features_test/app/frontend/javascript/components/PngImport.svelte
65
+ - templates/all_features_test/app/frontend/javascript/components/ReceiveFromChannel.svelte
66
+ - templates/all_features_test/app/frontend/javascript/components/SvelteOnRailsHelloWorld.svelte
67
+ - templates/all_features_test/app/frontend/javascript/components/SvgRawImport.svelte
68
+ - templates/all_features_test/app/frontend/javascript/components/sub/NestedComponent.svelte
69
+ - templates/all_features_test/app/frontend/javascript/nestedJavascript.js
70
+ - templates/all_features_test/app/frontend/javascript/nestedJavascriptToggled.js
71
+ - templates/all_features_test/app/views/svelte_on_rails_hello_world/_nav.html.erb
72
+ - templates/all_features_test/app/views/svelte_on_rails_hello_world/_styles.html.erb
73
+ - templates/all_features_test/app/views/svelte_on_rails_hello_world/backend_frontend_rendered.html.erb
74
+ - templates/all_features_test/app/views/svelte_on_rails_hello_world/index.html.erb
75
+ - templates/all_features_test/app/views/svelte_on_rails_hello_world/ssr_auto_rendered.html.erb
76
+ - templates/all_features_test/app/views/svelte_on_rails_hello_world/web_socket.html.erb
54
77
  - templates/config_base/app/frontend/ssr/ssr.js
55
78
  - templates/config_base/config/svelte_on_rails.yml
56
79
  - templates/config_base/vite-ssr.config.ts
57
- - templates/rails_vite_hello_world/app/channels/svelte_on_rails_channel.rb
58
- - templates/rails_vite_hello_world/app/controllers/svelte_on_rails_hello_world_controller.rb
59
- - templates/rails_vite_hello_world/app/frontend/images/svelte-on-rails-hello-world-england.png
60
- - templates/rails_vite_hello_world/app/frontend/images/svelte-on-rails-hello-world-face-smile-wink.svg
61
- - templates/rails_vite_hello_world/app/frontend/images/svelte-on-rails-hello-world-switzerland.jpg
62
- - templates/rails_vite_hello_world/app/frontend/javascript/components/JavascriptImport.svelte
63
- - templates/rails_vite_hello_world/app/frontend/javascript/components/JpgImport.svelte
64
- - templates/rails_vite_hello_world/app/frontend/javascript/components/ParentWithChild.svelte
65
- - templates/rails_vite_hello_world/app/frontend/javascript/components/PngImport.svelte
66
- - templates/rails_vite_hello_world/app/frontend/javascript/components/ReceiveFromChannel.svelte
67
- - templates/rails_vite_hello_world/app/frontend/javascript/components/SvelteOnRailsHelloWorld.svelte
68
- - templates/rails_vite_hello_world/app/frontend/javascript/components/SvgRawImport.svelte
69
- - templates/rails_vite_hello_world/app/frontend/javascript/components/sub/NestedComponent.svelte
70
- - templates/rails_vite_hello_world/app/frontend/javascript/nestedJavascript.js
71
- - templates/rails_vite_hello_world/app/frontend/javascript/nestedJavascriptToggled.js
72
- - templates/rails_vite_hello_world/app/views/svelte_on_rails_hello_world/_nav.html.erb
73
- - templates/rails_vite_hello_world/app/views/svelte_on_rails_hello_world/_styles.html.erb
74
- - templates/rails_vite_hello_world/app/views/svelte_on_rails_hello_world/backend_frontend_rendered.html.erb
75
- - templates/rails_vite_hello_world/app/views/svelte_on_rails_hello_world/index.html.erb
76
- - templates/rails_vite_hello_world/app/views/svelte_on_rails_hello_world/ssr_auto_rendered.html.erb
77
- - templates/rails_vite_hello_world/app/views/svelte_on_rails_hello_world/turbo_streams_channel.html.erb
78
80
  homepage: https://gitlab.com/sedl/svelte-on-rails
79
81
  licenses:
80
82
  - MIT
@@ -1,35 +0,0 @@
1
- class SvelteOnRailsHelloWorldController < ApplicationController
2
-
3
- def index
4
- end
5
-
6
- def turbo_stream_action
7
-
8
- if params['increase']
9
- SvelteOnRails::TurboStream.new.dispatch_event(
10
- event: 'click',
11
- selector: '.counter-button',
12
- component: '/javascript/components/ReceiveFromChannel'
13
- )
14
- render plain: 'increase-action streamed'
15
- elsif params['action_cable']
16
-
17
- ActionCable.server.broadcast("svelte_on_rails_channel",
18
- {
19
- eventDetail: { message: "Sent by ActionCable: äöü🤣🌴🌍漢字" },
20
- component: '/javascript/components/ReceiveFromChannel',
21
- }
22
- )
23
- #SvelteOnRails::ActionCable.new.dispatch_event(channel: "svelte_on_rails_channel", event_detail: { message: "Hello from SvelteChannel!" })
24
-
25
- render plain: 'ActionCable streaming'
26
- else
27
- SvelteOnRails::TurboStream.new.dispatch_event(
28
- event_detail: { message: "Sent by TurboStream: äöü🤣🌴🌍漢字", user: "Müller" }
29
- )
30
- render plain: 'component-action streamed'
31
- end
32
-
33
- end
34
-
35
- end
@@ -1,57 +0,0 @@
1
- <script lang="ts">
2
- import axios from "axios";
3
- import {addComponentStreamListener} from '@csedl/svelte-on-rails/src/componentStreamListener'
4
- import { onMount } from "svelte";
5
-
6
- let counter = $state(0)
7
- let results = $state([])
8
- let incrBtn
9
-
10
- onMount(() => {
11
- incrBtn.addEventListener('click', increaseCounter)
12
- });
13
-
14
- function callChannelAction(params) {
15
- results = []
16
- axios.get(`/svelte_on_rails_hello_world/turbo_stream_action?${params}`)
17
- .then(function (response) {
18
- results.push(
19
- {
20
- text: `server action called, status: ${response.status}`,
21
- class: 'called-for-action'
22
- })
23
- })
24
- }
25
-
26
- function increaseCounter() {
27
- counter += 1
28
- }
29
-
30
- const handleCableEvent = (ev) => {
31
- results.push(
32
- {
33
- text: `Message received from Server: ${JSON.stringify(ev.detail.message)}`,
34
- class: 'stream-action-received'
35
- }
36
- )
37
- };
38
-
39
- </script>
40
-
41
- <h1 use:addComponentStreamListener={handleCableEvent}>Test TurboStreams Channel</h1>
42
-
43
- <p>Actions that are triggered by SvelteOnRails::TurboStream channel from the server</p>
44
-
45
- <button onstream-action="{() => callChannelAction('increase=true')}">Call increase action over Channel</button>
46
-
47
- <button bind:this={incrBtn} class="counter-button" onclick="{increaseCounter}">increase {counter}</button>
48
-
49
- <button class="call-channel-action" onclick="{callChannelAction}">Action</button>
50
-
51
- <button onclick="{() => callChannelAction('action_cable=true')}">Call ActionCable Action</button>
52
-
53
- <ul class="results">
54
- {#each results as result}
55
- <li class="{result.class}">{result.text}</li>
56
- {/each}
57
- </ul>