thecore_generators 3.2.0 → 3.6.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.
@@ -0,0 +1,142 @@
1
+ require "yaml"
2
+
3
+ module Thecore
4
+ module Generators
5
+ # Shared companion-file behavior for Thecore's own custom-action
6
+ # generators: ensuring config/initializers/after_initialize.rb and
7
+ # assets.rb exist and carry the right require/precompile line, and
8
+ # writing the RailsAdmin action's locale entries. Introduced by
9
+ # RootActionGenerator (thecore_generators#11) and reused as-is by
10
+ # MemberActionGenerator (thecore_generators#12).
11
+ #
12
+ # Faithful Ruby port of the corresponding parts of thecore_code_extension's
13
+ # addRootAction.js/addMemberAction.js: the after_initialize.rb/assets.rb
14
+ # ensure-and-append logic, and the locale-entry merge behavior — broadened
15
+ # per thecore_generators#11's acceptance criteria to write into every
16
+ # *.yml file already present under the locales directory, not just
17
+ # en.yml/it.yml (the JS original only ever touched those two).
18
+ #
19
+ # Deliberately duplicates (rather than shares) the after_initialize.rb
20
+ # skeleton Thecore::Generators::AssociationWiring already writes — same
21
+ # structure/anchor, so the two compose fine regardless of which one
22
+ # happens to create the file first, but kept separate to avoid touching
23
+ # already-shipped Phase 1 code for this ticket.
24
+ module CompanionFiles
25
+ AFTER_INITIALIZE_TEMPLATE = <<~RUBY.freeze
26
+ Rails.application.configure do
27
+ config.after_initialize do
28
+ end
29
+ end
30
+ RUBY
31
+
32
+ ASSETS_TEMPLATE = <<~RUBY.freeze
33
+ # PLEASE, uncomment if needed.
34
+ # For Example: in the case there's a root action called tcp_debug, add the following lines to include css and javascripts for auto loading:
35
+ # Rails.application.config.assets.precompile += %w(
36
+ # main_tcp_debug.js
37
+ # main_tcp_debug.css
38
+ # )
39
+ RUBY
40
+
41
+ private
42
+
43
+ # Renders the shared view/JS/SCSS companion trio for a RailsAdmin
44
+ # custom action (root or member) into the workspace's fixed
45
+ # app/views/rails_admin/main, app/assets/javascripts/rails_admin/actions,
46
+ # and app/assets/stylesheets/rails_admin/actions directories — the same
47
+ # relative paths regardless of ATOM vs host-app context (only the
48
+ # action's own controller-config file, handled by the including
49
+ # generator, is placed differently: lib/root_actions vs
50
+ # config/root_actions, and similarly for member actions).
51
+ # Requires "action.html.erb.tt"/"action.js.tt"/"action.scss.tt" to be
52
+ # present in the including generator's own source_paths. Takes no
53
+ # argument deliberately: the template bodies themselves read the
54
+ # including generator's own `file_name` (via NamedBase) through the ERB
55
+ # binding `template` evaluates them in, so a separate action-name
56
+ # argument here would only rename the destination files while the
57
+ # content inside kept using `file_name` — a silent name/content
58
+ # mismatch. Callers needing a different action name for content must
59
+ # get there via their own `file_name`, not a parameter to this method.
60
+ def render_view_js_scss_companions!
61
+ template "action.html.erb.tt", File.join("app/views/rails_admin/main", "#{file_name}.html.erb")
62
+ template "action.js.tt", File.join("app/assets/javascripts/rails_admin/actions", "#{file_name}.js")
63
+ template "action.scss.tt", File.join("app/assets/stylesheets/rails_admin/actions", "#{file_name}.scss")
64
+ end
65
+
66
+ # Ensures config/initializers/after_initialize.rb exists (creating it
67
+ # from the skeleton above if absent) and that `require_line` is present
68
+ # inside its `config.after_initialize do ... end` block — idempotently.
69
+ def ensure_after_initialize_require!(require_line)
70
+ path = "config/initializers/after_initialize.rb"
71
+ full_path = File.join(destination_root, path)
72
+
73
+ create_file(path, AFTER_INITIALIZE_TEMPLATE) unless File.exist?(full_path)
74
+
75
+ content = File.read(full_path)
76
+ if content.include?(require_line)
77
+ say_status :skip, "#{path} already requires it", :blue
78
+ else
79
+ insert_into_file(path, " #{require_line}\n", after: /config\.after_initialize do\n/)
80
+ end
81
+ end
82
+
83
+ # Ensures config/initializers/assets.rb exists (creating it from the
84
+ # skeleton above if absent) and that `precompile_line` is appended to
85
+ # it — idempotently.
86
+ def ensure_assets_precompile_line!(precompile_line)
87
+ path = "config/initializers/assets.rb"
88
+ full_path = File.join(destination_root, path)
89
+
90
+ create_file(path, ASSETS_TEMPLATE) unless File.exist?(full_path)
91
+
92
+ content = File.read(full_path)
93
+ if content.include?(precompile_line)
94
+ say_status :skip, "#{path} already has the precompile line", :blue
95
+ else
96
+ append_to_file(path, "\n#{precompile_line}\n")
97
+ end
98
+ end
99
+
100
+ # Writes the RailsAdmin action's menu/title/breadcrumb locale entry
101
+ # (all three set to `title`, matching addRootAction.js/
102
+ # addMemberAction.js's mergeYaml behavior) under `admin.actions.<key>`
103
+ # into every *.yml file already present under config/locales — not just
104
+ # en.yml/it.yml, per thecore_generators#11's acceptance criteria. When
105
+ # the locales directory has no *.yml file yet, en.yml and it.yml are
106
+ # created first and then updated the same way.
107
+ def write_action_locale_entries!(key, title)
108
+ locales_dir = File.join(destination_root, "config", "locales")
109
+ existing = Dir.exist?(locales_dir) ? Dir.children(locales_dir).select { |f| f.end_with?(".yml") } : []
110
+
111
+ existing = %w[en.yml it.yml] if existing.empty?
112
+
113
+ existing.sort.each do |file|
114
+ rel_path = File.join("config", "locales", file)
115
+ full_path = File.join(destination_root, rel_path)
116
+ default_lang = File.basename(file, ".yml")
117
+
118
+ create_file(rel_path, "#{default_lang}:\n") unless File.exist?(full_path)
119
+ merge_action_locale_entry!(rel_path, default_lang, key, title)
120
+ end
121
+ end
122
+
123
+ # `lang` is the file's own single top-level key when it already has
124
+ # exactly one (the locale code, e.g. "en" in a Rails/Devise-style
125
+ # "devise.en.yml" whose filename does not equal its locale code) —
126
+ # falling back to `default_lang` (derived from the filename) only for
127
+ # an empty/freshly-created file, where there is nothing yet to read the
128
+ # real locale code from.
129
+ def merge_action_locale_entry!(rel_path, default_lang, key, title)
130
+ full_path = File.join(destination_root, rel_path)
131
+ data = YAML.load_file(full_path) || {}
132
+ lang = data.size == 1 ? data.keys.first : default_lang
133
+ data[lang] = {} unless data[lang].is_a?(Hash)
134
+ data[lang]["admin"] = {} unless data[lang]["admin"].is_a?(Hash)
135
+ data[lang]["admin"]["actions"] = {} unless data[lang]["admin"]["actions"].is_a?(Hash)
136
+ data[lang]["admin"]["actions"][key] = { "menu" => title, "title" => title, "breadcrumb" => title }
137
+
138
+ create_file(rel_path, YAML.dump(data), force: true, verbose: false)
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,61 @@
1
+ require "rails/generators/named_base"
2
+ require "generators/thecore/atom_aware"
3
+ require "generators/thecore/companion_files"
4
+ require "generators/thecore/action_companion"
5
+
6
+ module Thecore
7
+ module Generators
8
+ # `rails generate thecore:member_action NAME` — a Ruby port of
9
+ # thecore_code_extension's addMemberAction.js (thecore_generators#12),
10
+ # the Member Action counterpart to RootActionGenerator
11
+ # (thecore_generators#11). Shares the entire generator step sequence and
12
+ # placement/naming logic with it via Thecore::Generators::ActionCompanion
13
+ # (see that module and RootActionGenerator's own comment) — only this
14
+ # class's own `templates/action.rb.tt`/`action.html.erb.tt`/
15
+ # `action.js.tt` are Member-specific: the RailsAdmin `:member` action
16
+ # type and its XHR + form PATCH example, matching what
17
+ # `addMemberAction.js` produces today (not unified with Root's fetch +
18
+ # ActionCable-broadcast template).
19
+ #
20
+ # Discovered automatically by Rails::Generators' own namespace-by-path
21
+ # convention (`generators/thecore/member_action/member_action_generator.rb`
22
+ # → "thecore:member_action") — no Railtie registration needed, same as
23
+ # RootActionGenerator.
24
+ class MemberActionGenerator < Rails::Generators::NamedBase
25
+ include Thecore::Generators::AtomAware
26
+ include Thecore::Generators::CompanionFiles
27
+ include Thecore::Generators::ActionCompanion
28
+
29
+ action_kind "member_action"
30
+
31
+ source_root File.expand_path("templates", __dir__)
32
+
33
+ # Thin task methods, required on each class directly (see
34
+ # ActionCompanion's own comment for why) - each delegates to shared
35
+ # private logic there.
36
+ def validate_action_name!
37
+ validate_action_name_for_kind!
38
+ end
39
+
40
+ def create_action_file
41
+ template "action.rb.tt", action_file_path
42
+ end
43
+
44
+ def create_view_js_scss_companions
45
+ render_view_js_scss_companions!
46
+ end
47
+
48
+ def add_after_initialize_require
49
+ ensure_after_initialize_require!(require_line)
50
+ end
51
+
52
+ def add_assets_precompile_line
53
+ ensure_assets_precompile_line!(assets_precompile_line)
54
+ end
55
+
56
+ def add_locale_entries
57
+ write_action_locale_entries!(file_name, title_case_name)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,16 @@
1
+ <%%= stylesheet_link_tag 'rails_admin/actions/<%= file_name %>' %>
2
+ <%%= form_with(url: <%= file_name %>_path, html: { method: :patch }, class: "main", id: "<%= file_name %>-form") do |f| %>
3
+ <div class="form-actions row justify-content-end my-3">
4
+ <div class="col-sm-10">
5
+ <input name="return_to" type="hidden" value="<%%=edit_path(@abstract_model, @object.id)%>">
6
+ <button class="btn btn-primary" data-disable-with="Save" name="_save" type="submit">
7
+ <i class="fas fa-check"></i>
8
+ Test
9
+ </button>
10
+ </div>
11
+ </div>
12
+ <%% end %>
13
+ <!-- The button to test the xhr get request -->
14
+ <button id="<%= file_name %>-id" class="btn btn-primary" data-url="<%%= rails_admin.<%= file_name %>_path %>">Test <%= file_name %></button>
15
+ <div id="<%= file_name %>-response"></div>
16
+ <%%= javascript_include_tag "rails_admin/actions/<%= file_name %>" %>
@@ -0,0 +1,44 @@
1
+ var <%= action_name_camel_case %>Cable = null;
2
+ // If the <%= action_name_camel_case %>Function is already defined, then don't redefine it and don't attach it to the eventListener
3
+ if (typeof <%= action_name_camel_case %>Function !== 'function') {
4
+ function <%= action_name_camel_case %>Function(event) {
5
+ console.log('Hello from <%= file_name %>', event);
6
+ // Action Cable WebSocket connection only if <%= action_name_camel_case %>Cable is not already defined and valid
7
+ if (typeof <%= action_name_camel_case %>Cable !== 'object' || <%= action_name_camel_case %>Cable === null) {
8
+ <%= action_name_camel_case %>Cable = App.cable.subscriptions.create("ActivityLogChannel", {
9
+ connected() {
10
+ console.log("Connected to the channel:", this);
11
+ this.send({ message: '<%= file_name %> Client is connected', topic: "<%= file_name %>", namespace: "subscriptions" });
12
+ },
13
+ disconnected() {
14
+ console.log("<%= file_name %> Client Disconnected");
15
+ },
16
+ received(data) {
17
+ if(data["topic"] == "<%= file_name %>") {
18
+ console.log("<%= file_name %>", data);
19
+ document.getElementById('<%= file_name %>-response').innerHTML = data["message"];
20
+ }
21
+ }
22
+ });
23
+ }
24
+ // Send a message to the server
25
+ <%= action_name_camel_case %>Cable.send({ message: '<%= file_name %> Client is sending a message', topic: "<%= file_name %>", namespace: "subscriptions" });
26
+ // Attach a click event listener to the button which sends an XHR GET request and shows the response.
27
+ // The URL is read from the data-url attribute to avoid ERB interpolation in plain .js files.
28
+ document.getElementById('<%= file_name %>-id').addEventListener('click', function() {
29
+ var url = this.dataset.url;
30
+ var xhr = new XMLHttpRequest();
31
+ xhr.open('GET', url, true);
32
+ xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
33
+ xhr.onreadystatechange = function() {
34
+ if (xhr.readyState == 4 && xhr.status == 200) {
35
+ var response = JSON.parse(xhr.responseText);
36
+ document.getElementById('<%= file_name %>-response').innerHTML = response.message;
37
+ }
38
+ }
39
+ xhr.send();
40
+ });
41
+ }
42
+ }
43
+ // Attach the function to the eventListener
44
+ document.addEventListener('turbo:load', <%= action_name_camel_case %>Function);
@@ -0,0 +1,25 @@
1
+ RailsAdmin::Config::Actions.add_action "<%= file_name %>", :base, :member do
2
+ link_icon 'fas fa-file'
3
+ http_methods [:get, :patch]
4
+ # Customize visibility: show this action only for specific models.
5
+ # Example: visible only for the User model:
6
+ # visible do
7
+ # bindings[:object].is_a?(::User)
8
+ # end
9
+ # Or use the default authorization check:
10
+ visible? authorized?
11
+ # Adding the controller which is needed to compute calls from the ui
12
+ controller do
13
+ proc do
14
+ # if it's a form submission, then update the password
15
+ if !request.xhr? && request.patch?
16
+ flash[:success] = I18n.t("Successfully clicked on sample action")
17
+ # Redirect to the object
18
+ redirect_to index_path(model_name: @abstract_model.to_param)
19
+ elsif request.xhr? && request.get?
20
+ # Return a json response
21
+ render json: { message: "Hello from <%= file_name %>" }, status: :ok
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,38 @@
1
+ // Spinner
2
+ .loader {
3
+ width: 40px;
4
+ height: 40px;
5
+ position: relative;
6
+ margin: 0 auto;
7
+ }
8
+ .double-bounce1,
9
+ .double-bounce2 {
10
+ width: 100%;
11
+ height: 100%;
12
+ border-radius: 50%;
13
+ background-color: #333;
14
+ opacity: 0.6;
15
+ position: absolute;
16
+ top: 0;
17
+ left: 0;
18
+ animation: sk-bounce 2.0s infinite ease-in-out;
19
+ }
20
+ .double-bounce2 {
21
+ animation-delay: -1.0s;
22
+ }
23
+ @keyframes sk-bounce {
24
+ 0%,
25
+ 100% {
26
+ transform: scale(0.0);
27
+ }
28
+ 50% {
29
+ transform: scale(1.0);
30
+ }
31
+ }
32
+ // End Spinner
33
+ #<%= file_name %>-response {
34
+ border-radius: 1em;
35
+ display: flex;
36
+ flex-direction: column;
37
+ justify-content: center;
38
+ }
@@ -1,5 +1,6 @@
1
1
  require "rails/generators/active_record/migration/migration_generator"
2
2
  require "generators/thecore/atom_aware"
3
+ require "generators/thecore/association_wiring"
3
4
 
4
5
  module Thecore
5
6
  module Generators
@@ -9,17 +10,27 @@ module Thecore
9
10
  # Thecore::Generators::ModelGenerator for the namespace-resolution
10
11
  # mechanism, identical here).
11
12
  #
12
- # A pure wrap: 100% of ActiveRecord::Generators::MigrationGenerator's
13
- # migration-content logic (add/remove/create-table detection, attribute
14
- # parsing, templates) is inherited untouched. The only addition is
15
- # Thecore::Generators::AtomAware, redirecting the migration file into an
16
- # ATOM's db/migrate when one is detected from `Dir.pwd`/`--atom=NAME`, so
17
- # `rails generate migration AddBarToFoo bar:string` gets the same
18
- # context-aware placement standalone, not just via `rails generate model`.
13
+ # 100% of ActiveRecord::Generators::MigrationGenerator's migration-content
14
+ # logic (add/remove/create-table detection, attribute parsing, templates)
15
+ # is inherited untouched. On top of that:
16
+ # - Thecore::Generators::AtomAware redirects the migration file into an
17
+ # ATOM's db/migrate when one is detected from `Dir.pwd`/`--atom=NAME`,
18
+ # so `rails generate migration AddBarToFoo bar:string` gets the same
19
+ # context-aware placement standalone, not just via `rails generate
20
+ # model`.
21
+ # - Thecore::Generators::AssociationWiring detects `references`
22
+ # attributes and writes the missing inverse `has_many`/`has_one` side
23
+ # into the target model's concern (ADR 0003 in the thecore repo).
19
24
  class MigrationGenerator < ActiveRecord::Generators::MigrationGenerator
20
25
  include Thecore::Generators::AtomAware
26
+ include Thecore::Generators::AssociationWiring
21
27
 
22
28
  source_root ActiveRecord::Generators::MigrationGenerator.source_root
29
+
30
+ def create_migration_file
31
+ super
32
+ wire_inverse_associations_from_references
33
+ end
23
34
  end
24
35
  end
25
36
  end
@@ -1,5 +1,6 @@
1
1
  require "rails/generators/active_record/model/model_generator"
2
2
  require "generators/thecore/atom_aware"
3
+ require "generators/thecore/association_wiring"
3
4
 
4
5
  module Thecore
5
6
  module Generators
@@ -34,8 +35,16 @@ module Thecore
34
35
  # - Test file generation is never suppressed (no `--skip-test-framework`
35
36
  # equivalent) — `hook_for :test_framework` runs exactly as it does for
36
37
  # `active_record:model`.
38
+ # - Thecore::Generators::AssociationWiring detects `references`
39
+ # attributes and writes the missing inverse `has_many`/`has_one` side
40
+ # into the target model's concern (ADR 0003 in the thecore repo) —
41
+ # needed here too, not just in MigrationGenerator, because
42
+ # `rails generate model Foo x:references` creates its migration via
43
+ # ActiveRecord::Generators::ModelGenerator#create_migration_file, a
44
+ # different method than MigrationGenerator's own.
37
45
  class ModelGenerator < ActiveRecord::Generators::ModelGenerator
38
46
  include Thecore::Generators::AtomAware
47
+ include Thecore::Generators::AssociationWiring
39
48
 
40
49
  class_option :with_api_concern, type: :boolean, default: false,
41
50
  desc: "Scaffold a starter app/models/concerns/api/<model>.rb, included into the model " \
@@ -62,6 +71,11 @@ module Thecore
62
71
  add_opted_in_concerns
63
72
  end
64
73
 
74
+ def create_migration_file
75
+ super
76
+ wire_inverse_associations_from_references
77
+ end
78
+
65
79
  private
66
80
 
67
81
  # Faithful Ruby port of addModel.js's api_concern.rb/rails_admin_concern.rb
@@ -0,0 +1,80 @@
1
+ require "rails/generators/named_base"
2
+ require "generators/thecore/atom_aware"
3
+ require "generators/thecore/companion_files"
4
+ require "generators/thecore/action_companion"
5
+
6
+ module Thecore
7
+ module Generators
8
+ # `rails generate thecore:root_action NAME` — a Ruby port of
9
+ # thecore_code_extension's addRootAction.js (thecore_generators#11),
10
+ # producing the same end result from a terminal: the RailsAdmin root
11
+ # action file, its view/JS/SCSS companions, the after_initialize.rb
12
+ # require line, the assets.rb precompile line, and locale entries — with
13
+ # ATOM-aware placement via the same Thecore::Generators::AtomAware
14
+ # mechanism the Model/Migration generators already use.
15
+ #
16
+ # Discovered automatically by Rails::Generators' own namespace-by-path
17
+ # convention (this file's path derives the "thecore:root_action"
18
+ # namespace) — no Railtie registration needed, unlike
19
+ # ModelGenerator/MigrationGenerator, since there is no built-in Rails
20
+ # generator being overridden here.
21
+ #
22
+ # Unlike ModelGenerator/MigrationGenerator, Thecore::Generators::AtomAware
23
+ # only supplies `atom_dir`/`host_app_root` and the `--atom` option here —
24
+ # this generator does not override `destination_root` placement for a
25
+ # single fixed subpath the way Model/Migration's templates do, because the
26
+ # action file itself lives at a *different* relative path depending on
27
+ # context (ATOM: lib/root_actions/, host app: config/root_actions/ — see
28
+ # docs/adr/0001-main-app-actions-live-in-config.md in
29
+ # thecore_code_extension for why the host-app side avoids lib/). AtomAware
30
+ # still redirects `destination_root` into the ATOM dir when one is
31
+ # detected, so the view/JS/SCSS/locale/after_initialize/assets companions
32
+ # (fixed relative paths, shared with the host-app case) land in the right
33
+ # place automatically.
34
+ #
35
+ # The full generator step sequence (validate → create_action_file →
36
+ # create_view_js_scss_companions → add_after_initialize_require →
37
+ # add_assets_precompile_line → add_locale_entries) and everything about
38
+ # placement/naming lives in Thecore::Generators::ActionCompanion, shared
39
+ # with MemberActionGenerator (thecore_generators#12) — only this class's
40
+ # own `templates/action.rb.tt`/`action.html.erb.tt`/`action.js.tt` (the
41
+ # RailsAdmin `:root` action type and its fetch + ActionCable-broadcast
42
+ # example) are Root-specific.
43
+ class RootActionGenerator < Rails::Generators::NamedBase
44
+ include Thecore::Generators::AtomAware
45
+ include Thecore::Generators::CompanionFiles
46
+ include Thecore::Generators::ActionCompanion
47
+
48
+ action_kind "root_action"
49
+
50
+ source_root File.expand_path("templates", __dir__)
51
+
52
+ # Thin task methods, required on each class directly (see
53
+ # ActionCompanion's own comment for why) - each delegates to shared
54
+ # private logic there.
55
+ def validate_action_name!
56
+ validate_action_name_for_kind!
57
+ end
58
+
59
+ def create_action_file
60
+ template "action.rb.tt", action_file_path
61
+ end
62
+
63
+ def create_view_js_scss_companions
64
+ render_view_js_scss_companions!
65
+ end
66
+
67
+ def add_after_initialize_require
68
+ ensure_after_initialize_require!(require_line)
69
+ end
70
+
71
+ def add_assets_precompile_line
72
+ ensure_assets_precompile_line!(assets_precompile_line)
73
+ end
74
+
75
+ def add_locale_entries
76
+ write_action_locale_entries!(file_name, title_case_name)
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,13 @@
1
+ <%%= stylesheet_link_tag 'rails_admin/actions/<%= file_name %>' %>
2
+ <div class="card mb-3">
3
+ <div class="card-body">
4
+ <div class="response <%= file_name %>-response" id="<%= file_name %>-response">
5
+ </div>
6
+ <div class="loader d-none" id="<%= file_name %>-loader">
7
+ <div class="double-bounce1"></div>
8
+ <div class="double-bounce2"></div>
9
+ </div>
10
+ </div>
11
+ </div>
12
+ <button class="btn btn-primary" id="<%= file_name %>-id" data-url="<%%= rails_admin.<%= file_name %>_path %>">Click me</button>
13
+ <%%= javascript_include_tag "rails_admin/actions/<%= file_name %>" %>
@@ -0,0 +1,42 @@
1
+ var <%= action_name_camel_case %>Cable = null;
2
+ // If the <%= action_name_camel_case %>Function is already defined, then don't redefine it and don't attach it to the eventListener
3
+ if (typeof <%= action_name_camel_case %>Function !== 'function') {
4
+ function <%= action_name_camel_case %>Function(event) {
5
+ console.log('Hello from <%= file_name %>', event);
6
+ // Action Cable WebSocket connection only if <%= action_name_camel_case %>Cable is not already defined and valid
7
+ if (typeof <%= action_name_camel_case %>Cable !== 'object' || <%= action_name_camel_case %>Cable === null) {
8
+ <%= action_name_camel_case %>Cable = App.cable.subscriptions.create("ActivityLogChannel", {
9
+ connected() {
10
+ console.log("Connected to the channel:", this);
11
+ this.send({ message: '<%= file_name %> Client is connected', topic: "<%= file_name %>", namespace: "subscriptions" });
12
+ },
13
+ disconnected() {
14
+ console.log("<%= file_name %> Client Disconnected");
15
+ },
16
+ received(data) {
17
+ if(data["topic"] == "<%= file_name %>") {
18
+ console.log("<%= file_name %>", data);
19
+ document.getElementById('<%= file_name %>-response').innerHTML = data["message"];
20
+ }
21
+ }
22
+ });
23
+ }
24
+ // Send a message to the server
25
+ <%= action_name_camel_case %>Cable.send({ message: '<%= file_name %> Client is sending a message', topic: "<%= file_name %>", namespace: "subscriptions" });
26
+ // Attach a click event listener to the button which sends a fetch GET request and shows the response.
27
+ // The URL is read from the data-url attribute to avoid ERB interpolation in plain .js files.
28
+ document.getElementById('<%= file_name %>-id').addEventListener('click', function() {
29
+ var url = this.dataset.url;
30
+ document.getElementById('<%= file_name %>-loader').classList.remove('d-none');
31
+ fetch(url, { headers: { 'Accept': 'application/json' } })
32
+ .then(response => response.json())
33
+ .then(data => {
34
+ console.log(data);
35
+ document.getElementById('<%= file_name %>-response').innerHTML = data.message;
36
+ document.getElementById('<%= file_name %>-loader').classList.add('d-none');
37
+ });
38
+ });
39
+ }
40
+ }
41
+ // Attach the function to the eventListener
42
+ document.addEventListener('turbo:load', <%= action_name_camel_case %>Function);
@@ -0,0 +1,33 @@
1
+ RailsAdmin::Config::Actions.add_action "<%= file_name %>", :base, :root do
2
+ show_in_sidebar true
3
+ show_in_navigation false
4
+ breadcrumb_parent [nil]
5
+ # This ensures the action only shows up for authorized users
6
+ visible? authorized?
7
+ # Not a member action
8
+ member false
9
+ # Not a collection action
10
+ collection false
11
+ # Have a look at https://fontawesome.com/v5/search for available icons
12
+ link_icon 'fas fa-file'
13
+ # The controller which will be used to compute the action and the REST verbs it will respond to
14
+ http_methods [:get]
15
+ # Adding the controller which is needed to compute calls from the ui
16
+ controller do
17
+ proc do # This is needed because we need that this code is re-evaluated each time is called
18
+ if request.format.json?
19
+ # This is the code that is executed when the action is called
20
+ # It is executed in the context of the controller
21
+ # So you can access all the controller methods
22
+ # and instance variables
23
+ status = 200
24
+ message = "Hello World!"
25
+ # Note: ActivityLogChannel is expected to re-broadcast messages from the "messages" channel
26
+ ActionCable.server.broadcast("messages", { topic: :<%= file_name %>, status: status, message: message})
27
+ render json: {message: message}.to_json, status: status
28
+ else
29
+ # Renders the action.html.erb view for browser requests (HTML format)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,38 @@
1
+ // Spinner
2
+ .loader {
3
+ width: 40px;
4
+ height: 40px;
5
+ position: relative;
6
+ margin: 0 auto;
7
+ }
8
+ .double-bounce1,
9
+ .double-bounce2 {
10
+ width: 100%;
11
+ height: 100%;
12
+ border-radius: 50%;
13
+ background-color: #333;
14
+ opacity: 0.6;
15
+ position: absolute;
16
+ top: 0;
17
+ left: 0;
18
+ animation: sk-bounce 2.0s infinite ease-in-out;
19
+ }
20
+ .double-bounce2 {
21
+ animation-delay: -1.0s;
22
+ }
23
+ @keyframes sk-bounce {
24
+ 0%,
25
+ 100% {
26
+ transform: scale(0.0);
27
+ }
28
+ 50% {
29
+ transform: scale(1.0);
30
+ }
31
+ }
32
+ // End Spinner
33
+ #<%= file_name %>-response {
34
+ border-radius: 1em;
35
+ display: flex;
36
+ flex-direction: column;
37
+ justify-content: center;
38
+ }