Skip to content

Commit

Permalink
Implement client-side with Stimulus
Browse files Browse the repository at this point in the history
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 `<table>` 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
  • Loading branch information
seanpdoyle committed Oct 13, 2023
1 parent 51e0302 commit 3259381
Show file tree
Hide file tree
Showing 26 changed files with 131 additions and 35 deletions.
3 changes: 3 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ PATH
kaminari (>= 1.0)
sassc-rails (~> 2.1)
selectize-rails (~> 0.6)
stimulus-rails

GEM
remote: https://rubygems.org/
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions administrate.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 1 addition & 3 deletions app/assets/javascripts/administrate/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
7 changes: 0 additions & 7 deletions app/assets/javascripts/administrate/components/associative.js

This file was deleted.

5 changes: 0 additions & 5 deletions app/assets/javascripts/administrate/components/select.js

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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 }
11 changes: 11 additions & 0 deletions app/assets/javascripts/administrate/controllers/index.js
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Controller } from "@hotwired/stimulus"
import $ from "jquery"

export default class extends Controller {
connect() {
$(this.element).selectize({});
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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);
});
}
}
2 changes: 1 addition & 1 deletion app/views/administrate/application/_collection.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ to display a collection of resources in an HTML table.
[1]: http://www.rubydoc.info/gems/administrate/Administrate/Page/Collection
%>

<table aria-labelledby="<%= table_title %>">
<table aria-labelledby="<%= table_title %>" data-controller="table" data-action="click->table#visit keydown->table#visit">
<thead>
<tr>
<% collection_presenter.attribute_types.each do |attr_name, attr_type| %>
Expand Down
3 changes: 2 additions & 1 deletion app/views/fields/belongs_to/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@ that displays all possible records to associate with.
<div class="field-unit__field">
<%= 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}) %>
</div>
2 changes: 1 addition & 1 deletion app/views/fields/has_many/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ and is augmented with [Selectize].
<%= f.label field.attribute, for: "#{f.object_name}_#{field.attribute_key}" %>
</div>
<div class="field-unit__field">
<%= 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 %>
</div>
2 changes: 1 addition & 1 deletion app/views/fields/polymorphic/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ This partial renders an input element for polymorphic relationships.

<div class="field-unit__field">
<%= 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 %>
</div>
Expand Down
3 changes: 2 additions & 1 deletion app/views/fields/select/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -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}
)
%>
</div>
8 changes: 5 additions & 3 deletions config/importmap.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# 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"
5 changes: 5 additions & 0 deletions lib/administrate/engine.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require "importmap-rails"
require "stimulus-rails"
require "kaminari"
require "sassc-rails"
require "selectize-rails"
Expand Down Expand Up @@ -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

Expand Down
4 changes: 4 additions & 0 deletions lib/administrate/field/associative.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ def associated_class_name
end
end

def html_controller
"select"
end

private

def associated_dashboard
Expand Down
4 changes: 4 additions & 0 deletions lib/administrate/field/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ def html_class
self.class.html_class
end

def html_controller
nil
end

def name
attribute.to_s
end
Expand Down
4 changes: 4 additions & 0 deletions lib/administrate/field/has_one.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ def linkable?
data.try(:persisted?)
end

def html_controller
"select"
end

private

def resolver
Expand Down
4 changes: 4 additions & 0 deletions lib/administrate/field/select.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions spec/administrate/views/fields/has_many/_form_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
has_many = double(
attribute_key: :associated_object_ids,
attribute: :associated_objects,
html_controller: "select",
)

render(
Expand Down
6 changes: 4 additions & 2 deletions spec/administrate/views/fields/select/_edit_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
data: false,
selectable_options: [true, false, nil],
include_blank_option: false,
html_controller: "select",
)

render(
Expand All @@ -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
Expand All @@ -31,6 +32,7 @@
data: "Yes",
selectable_options: ["Yes", "No"],
include_blank_option: "Unknown",
html_controller: "select",
)

render(
Expand All @@ -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
Expand Down
12 changes: 12 additions & 0 deletions spec/lib/fields/belongs_to_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions spec/lib/fields/has_many_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions spec/lib/fields/polymorphic_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 17 additions & 0 deletions spec/lib/fields/select_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 3259381

Please sign in to comment.