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/administrate/views/fields/has_many/_form_spec.rb b/spec/administrate/views/fields/has_many/_form_spec.rb
index ccb473d865..42bd372a5d 100644
--- a/spec/administrate/views/fields/has_many/_form_spec.rb
+++ b/spec/administrate/views/fields/has_many/_form_spec.rb
@@ -6,6 +6,7 @@
has_many = double(
attribute_key: :associated_object_ids,
attribute: :associated_objects,
+ html_controller: "select",
)
render(
diff --git a/spec/administrate/views/fields/select/_edit_spec.rb b/spec/administrate/views/fields/select/_edit_spec.rb
index 045d575f65..f3e6ac1714 100644
--- a/spec/administrate/views/fields/select/_edit_spec.rb
+++ b/spec/administrate/views/fields/select/_edit_spec.rb
@@ -10,6 +10,7 @@
data: false,
selectable_options: [true, false, nil],
include_blank_option: false,
+ html_controller: "select",
)
render(
@@ -18,7 +19,7 @@
)
expect(rendered).to have_css(
- %{select[name="customer[email_subscriber]"]
+ %{select[name="customer[email_subscriber]"][data-controller~=select]
option[value="false"][selected="selected"]},
)
end
@@ -31,6 +32,7 @@
data: "Yes",
selectable_options: ["Yes", "No"],
include_blank_option: "Unknown",
+ html_controller: "select",
)
render(
@@ -39,7 +41,7 @@
)
expect(rendered).to have_css(
- %{select[name="customer[email_subscriber]"] option[value=""]},
+ %{select[name="customer[email_subscriber]"][data-controller~="select"] option[value=""]},
text: "Unknown",
)
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)