From d3244bc819f5f18c1150836e49f6646b9595d368 Mon Sep 17 00:00:00 2001 From: Sean Doyle Date: Wed, 11 Oct 2023 17:03:53 -0400 Subject: [PATCH] Implement client-side with Stimulus Depend on [stimulus-rails][] to make [@hotwired/stimulus][] controllers available on the client-side. First, install the server-side dependency. Next, add the client-side dependencies by adding entries to the [config/importmap.rb](./config/importmap.rb) for `@hotwired/stimulus` and `@hotwired/stimulus-loading`. Scaffold out the necessary files from installing `stimulus-rails`. Next, migrate the jQuery-powered code into controllers: the `select` controller to integrate with `selectize.js` and the `table` controller to manage `` elements. Finally, render each necessary field (`belongs_to`, `has_many`, `polymorphic`, `select`) with the `[data-controller="select"]` attribute (powered by a new `Field#html_controller` method. [stimulus-rails]: https://github.com/hotwired/stimulus-rails [@hotwired/stimulus]: https://stimulus.hotwired.dev --- Gemfile.lock | 3 +++ administrate.gemspec | 1 + .../javascripts/administrate/application.js | 4 +--- .../administrate/components/associative.js | 7 ------- .../administrate/components/select.js | 5 ----- .../administrate/controllers/application.js | 9 +++++++++ .../administrate/controllers/index.js | 11 +++++++++++ .../controllers/select_controller.js | 8 ++++++++ .../table_controller.js} | 18 ++++++++---------- .../application/_collection.html.erb | 2 +- app/views/fields/belongs_to/_form.html.erb | 3 ++- app/views/fields/has_many/_form.html.erb | 2 +- app/views/fields/polymorphic/_form.html.erb | 2 +- app/views/fields/select/_form.html.erb | 3 ++- config/importmap.rb | 8 +++++--- lib/administrate/engine.rb | 5 +++++ lib/administrate/field/associative.rb | 4 ++++ lib/administrate/field/base.rb | 4 ++++ lib/administrate/field/has_one.rb | 4 ++++ lib/administrate/field/select.rb | 4 ++++ spec/example_app/config/importmap.rb | 2 +- spec/lib/fields/belongs_to_spec.rb | 12 ++++++++++++ spec/lib/fields/has_many_spec.rb | 12 ++++++++++++ spec/lib/fields/polymorphic_spec.rb | 11 +++++++++++ spec/lib/fields/select_spec.rb | 17 +++++++++++++++++ 25 files changed, 127 insertions(+), 34 deletions(-) delete mode 100644 app/assets/javascripts/administrate/components/associative.js delete mode 100644 app/assets/javascripts/administrate/components/select.js create mode 100644 app/assets/javascripts/administrate/controllers/application.js create mode 100644 app/assets/javascripts/administrate/controllers/index.js create mode 100644 app/assets/javascripts/administrate/controllers/select_controller.js rename app/assets/javascripts/administrate/{components/table.js => controllers/table_controller.js} (55%) diff --git a/Gemfile.lock b/Gemfile.lock index f998b32134..0558584324 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -9,6 +9,7 @@ PATH kaminari (>= 1.0) sassc-rails (~> 2.1) selectize-rails (~> 0.6) + stimulus-rails GEM remote: https://rubygems.org/ @@ -305,6 +306,8 @@ GEM actionpack (>= 5.2) activesupport (>= 5.2) sprockets (>= 3.0.0) + stimulus-rails (1.3.0) + railties (>= 6.0.0) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) thor (1.2.2) diff --git a/administrate.gemspec b/administrate.gemspec index 334e377e3d..6a308f72fb 100644 --- a/administrate.gemspec +++ b/administrate.gemspec @@ -18,6 +18,7 @@ Gem::Specification.new do |s| s.add_dependency "activerecord", ">= 5.0" s.add_dependency "importmap-rails" + s.add_dependency "stimulus-rails" s.add_dependency "kaminari", ">= 1.0" s.add_dependency "sassc-rails", "~> 2.1" s.add_dependency "selectize-rails", "~> 0.6" diff --git a/app/assets/javascripts/administrate/application.js b/app/assets/javascripts/administrate/application.js index 9262515981..0abd398ec6 100644 --- a/app/assets/javascripts/administrate/application.js +++ b/app/assets/javascripts/administrate/application.js @@ -4,8 +4,6 @@ import jQuery from "jquery" import Rails from "jquery-ujs" import "selectize" -import "./components/associative" -import "./components/select" -import "./components/table" +import "./controllers" Rails(jQuery) diff --git a/app/assets/javascripts/administrate/components/associative.js b/app/assets/javascripts/administrate/components/associative.js deleted file mode 100644 index 2538cf92d4..0000000000 --- a/app/assets/javascripts/administrate/components/associative.js +++ /dev/null @@ -1,7 +0,0 @@ -import $ from "jquery" - -$(function() { - $('.field-unit--belongs-to select').selectize({}); - $(".field-unit--has-many select").selectize({}); - $('.field-unit--polymorphic select').selectize({}); -}); diff --git a/app/assets/javascripts/administrate/components/select.js b/app/assets/javascripts/administrate/components/select.js deleted file mode 100644 index 7d4427efa8..0000000000 --- a/app/assets/javascripts/administrate/components/select.js +++ /dev/null @@ -1,5 +0,0 @@ -import $ from "jquery" - -$(function() { - $('.field-unit--select select').selectize({}); -}); diff --git a/app/assets/javascripts/administrate/controllers/application.js b/app/assets/javascripts/administrate/controllers/application.js new file mode 100644 index 0000000000..1213e85c7a --- /dev/null +++ b/app/assets/javascripts/administrate/controllers/application.js @@ -0,0 +1,9 @@ +import { Application } from "@hotwired/stimulus" + +const application = Application.start() + +// Configure Stimulus development experience +application.debug = false +window.Stimulus = application + +export { application } diff --git a/app/assets/javascripts/administrate/controllers/index.js b/app/assets/javascripts/administrate/controllers/index.js new file mode 100644 index 0000000000..3a630baca8 --- /dev/null +++ b/app/assets/javascripts/administrate/controllers/index.js @@ -0,0 +1,11 @@ +// Import and register all your controllers from the importmap under controllers/* + +import { application } from "./controllers/application" + +// Eager load all controllers defined in the import map under controllers/**/*_controller +import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading" +eagerLoadControllersFrom("administrate/controllers", application) + +// Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!) +// import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading" +// lazyLoadControllersFrom("controllers", application) diff --git a/app/assets/javascripts/administrate/controllers/select_controller.js b/app/assets/javascripts/administrate/controllers/select_controller.js new file mode 100644 index 0000000000..e0f87a75f4 --- /dev/null +++ b/app/assets/javascripts/administrate/controllers/select_controller.js @@ -0,0 +1,8 @@ +import { Controller } from "@hotwired/stimulus" +import $ from "jquery" + +export default class extends Controller { + connect() { + $(this.element).selectize({}); + } +} diff --git a/app/assets/javascripts/administrate/components/table.js b/app/assets/javascripts/administrate/controllers/table_controller.js similarity index 55% rename from app/assets/javascripts/administrate/components/table.js rename to app/assets/javascripts/administrate/controllers/table_controller.js index 341cde5bd0..0aaacbb944 100644 --- a/app/assets/javascripts/administrate/components/table.js +++ b/app/assets/javascripts/administrate/controllers/table_controller.js @@ -1,12 +1,13 @@ +import { Controller } from "@hotwired/stimulus" import $ from "jquery" -$(function() { - var keycodes = { space: 32, enter: 13 }; +export default class extends Controller { + keycodes = { space: 32, enter: 13 } - var visitDataUrl = function(event) { + visit(event) { if (event.type == "click" || - event.keyCode == keycodes.space || - event.keyCode == keycodes.enter) { + event.keyCode == this.keycodes.space || + event.keyCode == this.keycodes.enter) { if (event.target.href) { return; @@ -18,8 +19,5 @@ $(function() { window.location = window.location.protocol + '//' + window.location.host + dataUrl; } } - }; - - $("table").on("click", ".js-table-row", visitDataUrl); - $("table").on("keydown", ".js-table-row", visitDataUrl); -}); + } +} diff --git a/app/views/administrate/application/_collection.html.erb b/app/views/administrate/application/_collection.html.erb index fafe8b01f4..440b580bf3 100644 --- a/app/views/administrate/application/_collection.html.erb +++ b/app/views/administrate/application/_collection.html.erb @@ -18,7 +18,7 @@ to display a collection of resources in an HTML table. [1]: http://www.rubydoc.info/gems/administrate/Administrate/Page/Collection %> -
+
<% collection_presenter.attribute_types.each do |attr_name, attr_type| %> diff --git a/app/views/fields/belongs_to/_form.html.erb b/app/views/fields/belongs_to/_form.html.erb index 5e8b62ca3f..134b85db4e 100644 --- a/app/views/fields/belongs_to/_form.html.erb +++ b/app/views/fields/belongs_to/_form.html.erb @@ -22,5 +22,6 @@ that displays all possible records to associate with.
<%= f.select(field.permitted_attribute, options_for_select(field.associated_resource_options, field.selected_option), - include_blank: field.include_blank_option) %> + include_blank: field.include_blank_option, + data: {controller: field.html_controller}) %>
diff --git a/app/views/fields/has_many/_form.html.erb b/app/views/fields/has_many/_form.html.erb index da36d904db..641580d4a0 100644 --- a/app/views/fields/has_many/_form.html.erb +++ b/app/views/fields/has_many/_form.html.erb @@ -23,7 +23,7 @@ and is augmented with [Selectize]. <%= f.label field.attribute, for: "#{f.object_name}_#{field.attribute_key}" %>
- <%= f.select(field.attribute_key, nil, {}, multiple: true) do %> + <%= f.select(field.attribute_key, nil, {}, multiple: true, data: {controller: field.html_controller}) do %> <%= options_for_select(field.associated_resource_options, field.selected_options) %> <% end %>
diff --git a/app/views/fields/polymorphic/_form.html.erb b/app/views/fields/polymorphic/_form.html.erb index edf0c62d5d..7aaa66122a 100644 --- a/app/views/fields/polymorphic/_form.html.erb +++ b/app/views/fields/polymorphic/_form.html.erb @@ -22,7 +22,7 @@ This partial renders an input element for polymorphic relationships.
<%= pf.hidden_field(:type, value: field.class.name) %> - <%= pf.select(:value) do %> + <%= pf.select(:value, {}, data: {controller: field.html_controller}) do %> <%= grouped_options_for_select(field.associated_resource_grouped_options, field.selected_global_id, prompt: true) %> <% end %>
diff --git a/app/views/fields/select/_form.html.erb b/app/views/fields/select/_form.html.erb index 87b38b47c0..63aa8ecf48 100644 --- a/app/views/fields/select/_form.html.erb +++ b/app/views/fields/select/_form.html.erb @@ -26,7 +26,8 @@ to be displayed on a resource's edit form page. field.selectable_options, field.data, ), - include_blank: field.include_blank_option + {include_blank: field.include_blank_option}, + data: {controller: field.html_controller} ) %> diff --git a/config/importmap.rb b/config/importmap.rb index 3727bf6cfe..c7ab3b72ce 100644 --- a/config/importmap.rb +++ b/config/importmap.rb @@ -1,11 +1,13 @@ # Pin npm packages by running ./bin/importmap -pin_all_from "app/javascript/administrate/components", under: "components" +pin_all_from Administrate::Engine.root.join("app/assets/javascripts/administrate/controllers"), under: "administrate/controllers" pin "administrate/application", preload: true -pin "jquery", to: "https://ga.jspm.io/npm:jquery@3.7.0/dist/jquery.js" -pin "jquery-ujs", to: "https://ga.jspm.io/npm:jquery-ujs@1.2.3/src/rails.js" +pin "jquery", to: "https://ga.jspm.io/npm:jquery@3.7.0/dist/jquery.js", preload: true +pin "jquery-ujs", to: "https://ga.jspm.io/npm:jquery-ujs@1.2.3/src/rails.js", preload: true +pin "@hotwired/stimulus", to: "stimulus.js", preload: true +pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true pin "selectize", to: "https://ga.jspm.io/npm:selectize.js@0.12.12/dist/js/selectize.js" pin "microplugin", to: "https://ga.jspm.io/npm:microplugin@0.0.3/src/microplugin.js" pin "sifter", to: "https://ga.jspm.io/npm:sifter@0.5.3/sifter.js" diff --git a/lib/administrate/engine.rb b/lib/administrate/engine.rb index bd511feec6..70cdd48bea 100644 --- a/lib/administrate/engine.rb +++ b/lib/administrate/engine.rb @@ -1,4 +1,5 @@ require "importmap-rails" +require "stimulus-rails" require "kaminari" require "sassc-rails" require "selectize-rails" @@ -29,6 +30,10 @@ class Engine < ::Rails::Engine initializer "administrate.assets.precompile" do |app| app.config.assets.precompile += [ "administrate/application.css", + "administrate/controllers/index.js", + "administrate/controllers/application.js", + "administrate/controllers/select_controller.js", + "administrate/controllers/table_controller.js", ] end diff --git a/lib/administrate/field/associative.rb b/lib/administrate/field/associative.rb index 73d0434e81..32085fab90 100644 --- a/lib/administrate/field/associative.rb +++ b/lib/administrate/field/associative.rb @@ -46,6 +46,10 @@ def associated_class_name end end + def html_controller + "select" + end + private def associated_dashboard diff --git a/lib/administrate/field/base.rb b/lib/administrate/field/base.rb index beca439a78..41081e0f0b 100644 --- a/lib/administrate/field/base.rb +++ b/lib/administrate/field/base.rb @@ -44,6 +44,10 @@ def html_class self.class.html_class end + def html_controller + nil + end + def name attribute.to_s end diff --git a/lib/administrate/field/has_one.rb b/lib/administrate/field/has_one.rb index 760b5ad2d2..28aa3ab0ac 100644 --- a/lib/administrate/field/has_one.rb +++ b/lib/administrate/field/has_one.rb @@ -48,6 +48,10 @@ def linkable? data.try(:persisted?) end + def html_controller + "select" + end + private def resolver diff --git a/lib/administrate/field/select.rb b/lib/administrate/field/select.rb index f959613bf1..57c0758904 100644 --- a/lib/administrate/field/select.rb +++ b/lib/administrate/field/select.rb @@ -35,6 +35,10 @@ def active_record_enum? def active_record_enum_values resource.class.defined_enums[attribute.to_s].map(&:first) end + + def html_controller + "select" + end end end end diff --git a/spec/example_app/config/importmap.rb b/spec/example_app/config/importmap.rb index 9d8498523e..0086a327b8 100644 --- a/spec/example_app/config/importmap.rb +++ b/spec/example_app/config/importmap.rb @@ -1,3 +1,3 @@ # Pin npm packages by running ./bin/importmap -pin "application", preload: true +pin "application" diff --git a/spec/lib/fields/belongs_to_spec.rb b/spec/lib/fields/belongs_to_spec.rb index 14be213bbd..9b871e0ed3 100644 --- a/spec/lib/fields/belongs_to_spec.rb +++ b/spec/lib/fields/belongs_to_spec.rb @@ -14,6 +14,18 @@ ) end + describe "#html_controller" do + it "returns select" do + page = :show + owner = double + field = Administrate::Field::BelongsTo.new(:owner, owner, page) + + html_controller = field.html_controller + + expect(html_controller).to eq("select") + end + end + describe "#to_partial_path" do it "returns a partial based on the page being rendered" do page = :show diff --git a/spec/lib/fields/has_many_spec.rb b/spec/lib/fields/has_many_spec.rb index 027681f69c..d011242ea0 100644 --- a/spec/lib/fields/has_many_spec.rb +++ b/spec/lib/fields/has_many_spec.rb @@ -4,6 +4,18 @@ require "support/mock_relation" describe Administrate::Field::HasMany do + describe "#html_controller" do + it "returns select" do + page = :show + items = double + field = Administrate::Field::HasMany.new(:items, items, page) + + html_controller = field.html_controller + + expect(html_controller).to eq("select") + end + end + describe "#to_partial_path" do it "returns a partial based on the page being rendered" do page = :show diff --git a/spec/lib/fields/polymorphic_spec.rb b/spec/lib/fields/polymorphic_spec.rb index e768a1c338..853b6f608d 100644 --- a/spec/lib/fields/polymorphic_spec.rb +++ b/spec/lib/fields/polymorphic_spec.rb @@ -7,6 +7,17 @@ describe Administrate::Field::Polymorphic do include FieldMatchers + describe "#html_controller" do + it "returns select" do + page = :show + field = Administrate::Field::Polymorphic.new(:foo, "hello", page) + + html_controller = field.html_controller + + expect(html_controller).to eq("select") + end + end + describe "#to_partial_path" do it "returns a partial based on the page being rendered" do page = :show diff --git a/spec/lib/fields/select_spec.rb b/spec/lib/fields/select_spec.rb index 36b9e63106..b7beea0ebc 100644 --- a/spec/lib/fields/select_spec.rb +++ b/spec/lib/fields/select_spec.rb @@ -2,6 +2,23 @@ require "administrate/field/select" describe Administrate::Field::Select do + describe "#html_controller" do + it "returns select" do + customer = create(:customer) + field = described_class.new( + :email_subscriber, + "yes", + :_page_, + resource: customer, + collection: ["no", "yes", "absolutely"], + ) + + html_controller = field.html_controller + + expect(html_controller).to eq("select") + end + end + describe "#selectable_options" do it "works when :collection is an array" do customer = create(:customer)