inline_forms 7.2.11 → 7.5.2
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +176 -0
- data/README.rdoc +4 -2
- data/app/assets/javascripts/inline_forms/inline_forms.js +23 -0
- data/app/assets/stylesheets/inline_forms/inline_forms.scss +32 -16
- data/app/controllers/concerns/versions_concern.rb +2 -1
- data/app/controllers/inline_forms_application_controller.rb +5 -1
- data/app/controllers/inline_forms_controller.rb +120 -24
- data/app/helpers/form_elements/ckeditor.rb +4 -30
- data/app/helpers/form_elements/plain_text.rb +23 -0
- data/app/helpers/form_elements/plain_text_area.rb +7 -3
- data/app/helpers/form_elements/text_area.rb +4 -44
- data/app/helpers/form_elements/text_area_without_ckeditor.rb +5 -4
- data/app/helpers/form_elements/text_field.rb +2 -2
- data/app/helpers/inline_forms_helper.rb +127 -71
- data/app/views/devise/sessions/_form.html.erb +4 -1
- data/app/views/inline_forms/_close.html.erb +6 -4
- data/app/views/inline_forms/_edit.html.erb +7 -40
- data/app/views/inline_forms/_list.html.erb +52 -39
- data/app/views/inline_forms/_new.html.erb +23 -11
- data/app/views/inline_forms/_show.html.erb +13 -11
- data/app/views/inline_forms/_versions_list.html.erb +4 -8
- data/app/views/inline_forms/create_list_frame.html.erb +3 -0
- data/app/views/inline_forms/field_edit.html.erb +3 -0
- data/app/views/inline_forms/field_show.html.erb +3 -0
- data/app/views/inline_forms/new_record.html.erb +3 -0
- data/app/views/inline_forms/row_close.html.erb +5 -0
- data/app/views/inline_forms/row_destroyed.html.erb +9 -0
- data/app/views/inline_forms/row_show.html.erb +3 -0
- data/app/views/inline_forms/versions_list_panel.html.erb +3 -0
- data/app/views/inline_forms/versions_panel.html.erb +3 -0
- data/app/views/layouts/application.html.erb +0 -1
- data/app/views/layouts/inline_forms.html.erb +10 -1
- data/bin/inline_forms +22 -1
- data/bin/inline_forms_installer_core.rb +38 -3
- data/docs/ujs-to-turbo.md +193 -0
- data/lib/generators/USAGE +2 -2
- data/lib/generators/assets/stylesheets/inline_forms.scss +32 -16
- data/lib/inline_forms/version.rb +1 -1
- data/lib/inline_forms.rb +58 -2
- data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_field_turbo_test.rb +74 -0
- data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_name_list_test.rb +73 -0
- data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_photos_pagination_test.rb +199 -15
- data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_row_turbo_test.rb +94 -0
- data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_top_level_new_test.rb +100 -0
- data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_versions_turbo_test.rb +27 -0
- data/lib/installer_templates/example_app_tests/test/models/example_app_plain_text_rich_text_edge_cases_test.rb +46 -0
- data/lib/installer_templates/example_app_views/apartments/name_list.html.erb +26 -0
- data/lib/installer_templates/example_app_views/inline_forms/_header.html.erb +45 -0
- data/test/inline_forms_generator_test.rb +10 -0
- data/test/plain_text_configuration_test.rb +90 -0
- metadata +21 -5
- data/app/views/inline_forms/edit.js.erb +0 -1
- data/app/views/inline_forms/show_element.js.erb +0 -1
- data/app/views/inline_forms/update.js.erb +0 -1
- data/lib/generators/assets/javascripts/ckeditor/config.js +0 -72
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
<div class="new_record"">
|
|
2
2
|
<div id="flash" class="row">
|
|
3
|
-
|
|
3
|
+
<%# Only validation feedback from InlineFormsController#create (flash.now); %>
|
|
4
|
+
<%# never dump session flash (:notice / :alert from Devise, etc.). %>
|
|
5
|
+
<% %w[header error success].each do |key| %>
|
|
6
|
+
<% value = flash[key] %>
|
|
7
|
+
<% next if value.blank? %>
|
|
4
8
|
<div class="flash <%= key %>">
|
|
5
9
|
<ul>
|
|
6
10
|
<% if value.is_a?(Array) %>
|
|
@@ -25,10 +29,17 @@
|
|
|
25
29
|
</div>
|
|
26
30
|
</div>
|
|
27
31
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
<% new_form_opts = { multipart: true, class: "edit_form" } %>
|
|
33
|
+
<% if @turbo_frame %>
|
|
34
|
+
<% new_form_opts[:data] = { turbo: true, turbo_frame: @update_span } %>
|
|
35
|
+
<% else %>
|
|
36
|
+
<% new_form_opts[:remote] = true %>
|
|
37
|
+
<% end %>
|
|
38
|
+
<%= form_tag polymorphic_path(@Klass,
|
|
39
|
+
update: @update_span,
|
|
40
|
+
parent_class: @parent_class,
|
|
41
|
+
parent_id: @parent_id),
|
|
42
|
+
new_form_opts do -%>
|
|
32
43
|
<% attributes = @inline_forms_attribute_list || @object.inline_forms_attribute_list -%>
|
|
33
44
|
<% attributes.each do | attribute, name, form_element | -%>
|
|
34
45
|
<% unless form_element.to_sym == :associated || form_element.to_sym == :tree || (cancan_enabled? && cannot?(:read, @object, attribute)) -%>
|
|
@@ -56,12 +67,13 @@
|
|
|
56
67
|
|
|
57
68
|
</div>
|
|
58
69
|
<div class='small-11 column' >
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
70
|
+
<% cancel_path = polymorphic_path(@Klass,
|
|
71
|
+
update: @update_span,
|
|
72
|
+
parent_class: @parent_class,
|
|
73
|
+
parent_id: @parent_id,
|
|
74
|
+
ul_needed: true) %>
|
|
75
|
+
<% cancel_opts = @turbo_frame ? inline_forms_turbo_link_data(@update_span) : { remote: true } %>
|
|
76
|
+
<%= link_to cancel_path, cancel_opts do -%>
|
|
65
77
|
<input type="button" name="cancel" value="cancel" class="button alert" />
|
|
66
78
|
<% end %>
|
|
67
79
|
<%= submit_tag "ok", :class => "button "-%>
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
<%# Scalar field inline edit uses <turbo-frame> + HTML responses (Step 3). %>
|
|
2
|
+
<% @inline_forms_turbo_field = true %>
|
|
1
3
|
<div class="row">
|
|
2
4
|
<% if (INLINE_FORMS_SHOW_INDENT rescue true) %>
|
|
3
5
|
<div class='medium-1 large-1 column'>
|
|
@@ -13,7 +15,7 @@
|
|
|
13
15
|
<%= h(@object._presentation) -%>
|
|
14
16
|
</div>
|
|
15
17
|
<div class="small-1 column close_link" >
|
|
16
|
-
<%= close_link(@object, @update_span) -%>
|
|
18
|
+
<%= close_link(@object, @update_span, turbo_row: @inline_forms_turbo_row) -%>
|
|
17
19
|
</div>
|
|
18
20
|
</div>
|
|
19
21
|
<% end %>
|
|
@@ -67,16 +69,16 @@
|
|
|
67
69
|
<div class='medium-1 large-1 column'>
|
|
68
70
|
|
|
69
71
|
</div>
|
|
70
|
-
<
|
|
72
|
+
<turbo-frame id="<%= css_class_id -%>" class="small-11 column">
|
|
71
73
|
<% else %>
|
|
72
|
-
<
|
|
74
|
+
<turbo-frame id="<%= css_class_id -%>" class="small-12 column">
|
|
73
75
|
<% end %>
|
|
74
76
|
<%= render :partial => "inline_forms/list",
|
|
75
77
|
:locals => { :parent_class => @object.class,
|
|
76
78
|
:parent_id => @object.id,
|
|
77
79
|
:attribute => attribute,
|
|
78
80
|
:form_element => form_element } %>
|
|
79
|
-
</
|
|
81
|
+
</turbo-frame>
|
|
80
82
|
</div>
|
|
81
83
|
<% else %>
|
|
82
84
|
<% if form_element == :has_one %>
|
|
@@ -84,13 +86,13 @@
|
|
|
84
86
|
<div class='medium-1 large-1 column'>
|
|
85
87
|
|
|
86
88
|
</div>
|
|
87
|
-
<
|
|
89
|
+
<turbo-frame id="<%= css_class_id -%>" class="small-11 column">
|
|
88
90
|
<%= render :partial => "inline_forms/list",
|
|
89
91
|
:locals => { :parent_class => @object.class,
|
|
90
92
|
:parent_id => @object.id,
|
|
91
93
|
:attribute => attribute,
|
|
92
94
|
:form_element => form_element } %>
|
|
93
|
-
</
|
|
95
|
+
</turbo-frame>
|
|
94
96
|
</div>
|
|
95
97
|
<% else %>
|
|
96
98
|
<div class="row <%= cycle('odd', 'even') %>">
|
|
@@ -104,9 +106,9 @@
|
|
|
104
106
|
<% end %>
|
|
105
107
|
</div>
|
|
106
108
|
<div class='medium-7 large-7 column' >
|
|
107
|
-
<
|
|
109
|
+
<turbo-frame id="<%= css_class_id -%>">
|
|
108
110
|
<%= send("#{form_element}_show", @object, attribute) -%>
|
|
109
|
-
</
|
|
111
|
+
</turbo-frame>
|
|
110
112
|
</div>
|
|
111
113
|
</div>
|
|
112
114
|
<% end %>
|
|
@@ -117,9 +119,9 @@
|
|
|
117
119
|
<% end %>
|
|
118
120
|
<% if can? :list_versions, @object %>
|
|
119
121
|
<% css_class_id = "#{@object.class.name.underscore}_#{@object.id}_versions" -%>
|
|
120
|
-
<
|
|
121
|
-
<%= render
|
|
122
|
-
</
|
|
122
|
+
<turbo-frame id="<%= css_class_id -%>">
|
|
123
|
+
<%= render "versions" %>
|
|
124
|
+
</turbo-frame>
|
|
123
125
|
<% end %>
|
|
124
126
|
<div class="record_footer"></div>
|
|
125
127
|
</div>
|
|
@@ -46,14 +46,10 @@
|
|
|
46
46
|
<div class="row <%= cycle('odd', 'even') %>">
|
|
47
47
|
<div class="small-1 column">
|
|
48
48
|
<% if entry[:kind] == :primary %>
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
),
|
|
54
|
-
:remote => true,
|
|
55
|
-
:method => :post
|
|
56
|
-
%>
|
|
49
|
+
<% row_id = "#{@object.class.name.underscore}_#{@object.id}" %>
|
|
50
|
+
<%= link_to t("inline_forms.view.restore"),
|
|
51
|
+
send("revert_#{@object.class.to_s.underscore}_path", version, update: row_id),
|
|
52
|
+
inline_forms_turbo_link_data(row_id, method: :post) %>
|
|
57
53
|
<% else %>
|
|
58
54
|
|
|
59
55
|
<% end %>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<turbo-frame id="<%= @update_span %>">
|
|
2
|
+
<div class="row odd top-level">
|
|
3
|
+
<div class="small-12 column">
|
|
4
|
+
<%= link_to t("inline_forms.view.undo"),
|
|
5
|
+
send("revert_#{@Klass.to_s.underscore}_path", @undo_version, update: @update_span),
|
|
6
|
+
inline_forms_turbo_link_data(@update_span, method: :post).merge(class: "button") %>
|
|
7
|
+
</div>
|
|
8
|
+
</div>
|
|
9
|
+
</turbo-frame>
|
|
@@ -12,6 +12,16 @@
|
|
|
12
12
|
|
|
13
13
|
<body>
|
|
14
14
|
<%= render "inline_forms/header" %>
|
|
15
|
+
<%# Read string keys (FlashHash stores strings); avoid `notice`/`alert` helpers %>
|
|
16
|
+
<%# so we do not depend on add_flash_types wiring in every layout context. %>
|
|
17
|
+
<% flash_notice = flash["notice"] %>
|
|
18
|
+
<% flash_alert = flash["alert"] %>
|
|
19
|
+
<% if flash_notice.present? || flash_alert.present? %>
|
|
20
|
+
<div class="row" id="inline_forms_layout_flash" role="status">
|
|
21
|
+
<% if flash_notice.present? %><div class="flash notice"><%= flash_notice %></div><% end %>
|
|
22
|
+
<% if flash_alert.present? %><div class="flash alert"><%= flash_alert %></div><% end %>
|
|
23
|
+
</div>
|
|
24
|
+
<% end %>
|
|
15
25
|
<%= render "/inline_forms_tabs" %>
|
|
16
26
|
<div id="outer_container">
|
|
17
27
|
<%= yield %>
|
|
@@ -28,6 +38,5 @@
|
|
|
28
38
|
window.Turbo = Turbo
|
|
29
39
|
</script>
|
|
30
40
|
<%= javascript_include_tag "https://unpkg.com/trix@1.3.1/dist/trix.js" %>
|
|
31
|
-
<%= javascript_include_tag Ckeditor.cdn_url if defined?(Ckeditor) %>
|
|
32
41
|
</body>
|
|
33
42
|
</html>
|
data/bin/inline_forms
CHANGED
|
@@ -110,7 +110,28 @@ module InlineForms
|
|
|
110
110
|
|
|
111
111
|
app_template_file = File.join(File.dirname(__FILE__), 'inline_forms_app_template.rb')
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
# Prefer a Rails 7.0.x generator when one is installed locally: the
|
|
114
|
+
# generated `Gemfile`/`config/application.rb` pin to `rails ~> 7.0.0`,
|
|
115
|
+
# so running `rails new` from a newer system Rails (e.g. 8.0.x) emits
|
|
116
|
+
# 7.1+/8.0-only configuration (`load_defaults 8.0`,
|
|
117
|
+
# `config.autoload_lib(...)`) that aborts on the next `bundle exec`.
|
|
118
|
+
# The installer_core also patches `config/application.rb` defensively;
|
|
119
|
+
# the explicit version selector is the cleaner first line of defense.
|
|
120
|
+
require 'rubygems'
|
|
121
|
+
compatible_rails =
|
|
122
|
+
begin
|
|
123
|
+
Gem::Specification
|
|
124
|
+
.find_all_by_name("rails")
|
|
125
|
+
.map(&:version)
|
|
126
|
+
.select { |v| v >= Gem::Version.new("7.0") && v < Gem::Version.new("7.1") }
|
|
127
|
+
.max
|
|
128
|
+
rescue StandardError
|
|
129
|
+
nil
|
|
130
|
+
end
|
|
131
|
+
rails_invocation = compatible_rails ? "rails _#{compatible_rails}_" : "rails"
|
|
132
|
+
say "Generating app with: #{rails_invocation} new ...", :green
|
|
133
|
+
|
|
134
|
+
if ! run("#{rails_invocation} new #{app_name} -m #{app_template_file} --skip-bundle --skip-bootsnap --javascript=importmap")
|
|
114
135
|
say "Rails could not create the app '#{app_name}', maybe because it is a reserved word...", :red # TODO ROYTJE MAKE ERROR MESSAGE MORE RELEVANT # Rails could not create the app 'MyApp', maybe because it is a reserved word..
|
|
115
136
|
exit 1
|
|
116
137
|
end
|
|
@@ -5,12 +5,33 @@ GENERATOR_PATH = File.dirname(File.expand_path(__FILE__)) + '/../'
|
|
|
5
5
|
remove_file 'Gemfile' if File.exist?('Gemfile')
|
|
6
6
|
create_file 'Gemfile', "# created by inline_forms #{ENV['inline_forms_version']} on #{Date.today}\n"
|
|
7
7
|
|
|
8
|
+
# `rails new` is invoked with whatever the system `rails` binary points at
|
|
9
|
+
# (often Rails 8.x once it lands in the global gemset), so the generated
|
|
10
|
+
# `config/application.rb` may carry Rails 7.1+/8.0 idioms (`load_defaults
|
|
11
|
+
# 8.0`, `config.autoload_lib(...)`). The Gemfile we write below pins
|
|
12
|
+
# `rails ~> 7.0.0`, so Rails 7.0 must be able to interpret application.rb;
|
|
13
|
+
# otherwise the first `bundle exec rails …` aborts with `Unknown version
|
|
14
|
+
# "8.0"` or `NoMethodError: undefined method 'autoload_lib'`.
|
|
15
|
+
if File.exist?('config/application.rb')
|
|
16
|
+
gsub_file 'config/application.rb',
|
|
17
|
+
/config\.load_defaults\s+\d+\.\d+/,
|
|
18
|
+
'config.load_defaults 7.0'
|
|
19
|
+
# Strip Rails 7.1+ `config.autoload_lib(ignore: ...)` (and any surrounding
|
|
20
|
+
# explanatory comment block). Not supported on Rails 7.0.
|
|
21
|
+
gsub_file 'config/application.rb',
|
|
22
|
+
/^\s*#[^\n]*\n(\s*#[^\n]*\n)*\s*config\.autoload_lib\([^)]*\)\s*\n/,
|
|
23
|
+
""
|
|
24
|
+
gsub_file 'config/application.rb',
|
|
25
|
+
/^\s*config\.autoload_lib\([^)]*\)\s*\n/,
|
|
26
|
+
""
|
|
27
|
+
end
|
|
28
|
+
|
|
8
29
|
add_source 'https://rubygems.org'
|
|
9
30
|
|
|
10
31
|
gem 'cancancan'
|
|
11
32
|
gem 'carrierwave', '~> 3.1'
|
|
12
|
-
gem 'devise
|
|
13
|
-
gem 'devise'
|
|
33
|
+
gem 'devise', '~> 5.0'
|
|
34
|
+
gem 'devise-i18n', '~> 1.16'
|
|
14
35
|
gem 'autoprefixer-rails'
|
|
15
36
|
# foundation-rails 6.7+ uses Dart Sass (`sass:math`); sass-rails/sassc removed.
|
|
16
37
|
# Visually tuned against foundation-rails ~> 6.6.2; current pin ~> 6.9 (6.9.0.x).
|
|
@@ -443,7 +464,7 @@ sleep 1 # unique migration timestamps per generator
|
|
|
443
464
|
generate "inline_forms", "InlineFormsKey name:string inline_forms_translations:has_many inline_forms_translations:associated _enabled:yes _presentation:\#{name}"
|
|
444
465
|
sleep 1
|
|
445
466
|
generate "inline_forms", "InlineFormsTranslation inline_forms_key:belongs_to inline_forms_locale:dropdown value:text interpolations:text is_proc:boolean _presentation:\#{value}"
|
|
446
|
-
#
|
|
467
|
+
# Plain long text uses :plain_text; ActionText-backed fields use :rich_text.
|
|
447
468
|
sleep 1 # to get unique migration number
|
|
448
469
|
create_file "db/migrate/" +
|
|
449
470
|
Time.now.utc.strftime("%Y%m%d%H%M%S") +
|
|
@@ -756,6 +777,19 @@ if ENV['install_example'] == 'true'
|
|
|
756
777
|
|
|
757
778
|
remove_file 'public/index.html'
|
|
758
779
|
|
|
780
|
+
say "- Apartment name list demo (field-level inline edit without _show)..."
|
|
781
|
+
inject_into_file "app/controllers/apartments_controller.rb",
|
|
782
|
+
"\n skip_load_and_authorize_resource only: :name_list\n\n def name_list\n authorize! :read, Apartment\n @apartments = Apartment.accessible_by(current_ability).order(:id).limit(10)\n end\n",
|
|
783
|
+
after: "set_tab :apartment\n"
|
|
784
|
+
|
|
785
|
+
example_views_root = File.join(GENERATOR_PATH, "lib/installer_templates/example_app_views")
|
|
786
|
+
Dir.glob(File.join(example_views_root, "**", "*")).sort.each do |abs|
|
|
787
|
+
next unless File.file?(abs)
|
|
788
|
+
rel = abs.delete_prefix(example_views_root + File::SEPARATOR).tr("\\", "/")
|
|
789
|
+
create_file File.join("app/views", rel), File.read(abs)
|
|
790
|
+
end
|
|
791
|
+
|
|
792
|
+
route 'get "apartments/name_list", to: "apartments#name_list", as: :apartment_name_list'
|
|
759
793
|
route "root :to => 'apartments#index'"
|
|
760
794
|
|
|
761
795
|
say "- Adding example app regression tests (bundle exec rails test)..."
|
|
@@ -768,6 +802,7 @@ if ENV['install_example'] == 'true'
|
|
|
768
802
|
say "\nDone! Example app (Photo + Apartment) is ready.", :yellow
|
|
769
803
|
say " bundle exec rails test # example regression tests", :yellow
|
|
770
804
|
say " bundle exec rails s # then http://localhost:3000/apartments", :yellow
|
|
805
|
+
say " More menu → Apartment names (first 10) # /apartments/name_list", :yellow
|
|
771
806
|
say " Log in: #{ENV["email"]} / #{ENV["password"]}", :yellow
|
|
772
807
|
end
|
|
773
808
|
# done!
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# UJS → Turbo migration checklist
|
|
2
|
+
|
|
3
|
+
Track progress toward full Turbo integration and removal of jQuery UJS from inline_forms generated apps.
|
|
4
|
+
|
|
5
|
+
**Current gem version:** see `lib/inline_forms/version.rb` (Step 3 complete in **7.5.0** except tree-related `*.js.erb` — Step 4)
|
|
6
|
+
|
|
7
|
+
**Architecture today:** almost every inline interaction is `remote: true` → `format.js` → `*.js.erb` doing `$('#<update_span>').html(...)`. Turbo is loaded as an ES module with **`Turbo.session.drive = false`**. One vertical slice (nested has_many list pagination) uses **`<turbo-frame>`**.
|
|
8
|
+
|
|
9
|
+
**Target end state:** Turbo Frames/Streams for all swaps; **`format.html`** / **`format.turbo_stream`** responses; no `*.js.erb`; no `jquery_ujs` or `jquery.remotipart`; Drive enabled.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Step 1 — Turbo wired (DONE)
|
|
14
|
+
|
|
15
|
+
- [x] `gem 'turbo-rails'` in installer Gemfile (`bin/inline_forms_installer_core.rb`)
|
|
16
|
+
- [x] Turbo loaded as `<script type="module">` in `layouts/inline_forms.html.erb` and `layouts/application.html.erb`
|
|
17
|
+
- [x] `Turbo.session.drive = false` (UJS still owns navigation)
|
|
18
|
+
- [x] Turbo **not** in Sprockets bundle (`app/assets/javascripts/inline_forms/inline_forms.js`) — ESM parse-error lesson from 7.1.1
|
|
19
|
+
- [x] Smoke test: `lib/installer_templates/example_app_tests/test/integration/example_app_turbo_layout_test.rb`
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Step 2 — Nested list + pagination as Turbo Frame (DONE)
|
|
24
|
+
|
|
25
|
+
- [x] `_list.html.erb`: nested has_many container is `<turbo-frame id="…_list">` when `parent_class` present
|
|
26
|
+
- [x] Nested pagination: no `:remote => true`; `update=` param matches frame id (`…_list` suffix)
|
|
27
|
+
- [x] `InlineFormsController#index`: HTML for nested frame requests; `turbo_rails/frame` vs `inline_forms` layout negotiation
|
|
28
|
+
- [x] **Nested rows (7.4.2):** per-row `<turbo-frame>` + Turbo presentation links; **`row_html_turbo_allowed?`** enables **`format.html`** row open/close for **`not_accessible_through_html?`** models when **`params[:update]`** is a nested associated row id (`apartment_<aid>_photo_<pid>`). Removed row-level **`data-turbo="false"`** (field cancel + pagination use Turbo inside nested frames).
|
|
29
|
+
- [x] Top-level rows must **not** carry `data-turbo="false"` (would poison inner frames — 7.2.3)
|
|
30
|
+
- [x] Smoke test: `example_app_apartment_photos_pagination_test.rb`
|
|
31
|
+
- [x] Example seed data (Konferensha + photos) for pagination assertions
|
|
32
|
+
|
|
33
|
+
**Explicitly deferred in this step:** top-level index lists (`/apartments`) stay full-page + UJS row open.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Step 3 — Per-row / per-field inline-edit lifecycle
|
|
38
|
+
|
|
39
|
+
Convert the **`show → edit → update → show_element → close`** cycle without UJS.
|
|
40
|
+
|
|
41
|
+
### DOM contract (unchanged)
|
|
42
|
+
|
|
43
|
+
- Every editable region needs a stable id: `{model}_{id}_{attribute}` (e.g. `apartment_5_name`)
|
|
44
|
+
- `params[:update]` must match that id (set by `link_to_inline_edit`, `_edit.html.erb`, etc.)
|
|
45
|
+
|
|
46
|
+
### Row-level (stock list → full `_show` panel)
|
|
47
|
+
|
|
48
|
+
- [x] Wrap each top-level list row in `<turbo-frame id="apartment_<id>">` (`_list.html.erb` when `parent_class` is nil)
|
|
49
|
+
- [x] Row title link: GET `show` → HTML `row_show` / `_show` inside frame (no `:remote`; `data-turbo` + `data-turbo-frame`)
|
|
50
|
+
- [x] `InlineFormsController#show` (full record, no `params[:attribute]`): `format.html` → `row_show` / `row_close` + `turbo_rails/frame` when `turbo_frame_request?`, else full `inline_forms` layout; `format.js` still renders `show.js.erb` / `close.js.erb` for legacy callers
|
|
51
|
+
- [x] `close_link` and `_close` presentation link: Turbo when `@inline_forms_turbo_row` (row HTML path)
|
|
52
|
+
- [x] `soft_delete`, `soft_restore`, `destroy`, `revert`: `format.html` → `row_close` / `row_destroyed` (+ Turbo toolbar links); `format.js` kept for non-frame callers only
|
|
53
|
+
- [x] Remove `edit.js.erb`, `update.js.erb`, `show_element.js.erb` (field lifecycle is Turbo HTML only)
|
|
54
|
+
- [ ] Remove `show.js.erb`, `close.js.erb`, `record_destroyed.js.erb`, `show_undo.js.erb` — **`_tree.html.erb` still uses UJS row open (Step 4)**
|
|
55
|
+
- [x] Remove `:remote => true` from nested `_list` / `_close` / toolbar where migrated (row toolbar, versions, nested `+` use Turbo; `_tree` / top-level `new` UJS deferred to Step 4)
|
|
56
|
+
- [x] Remove `data-turbo="false"` from nested rows once nested inline-edit is Turbo-native (7.4.2)
|
|
57
|
+
|
|
58
|
+
### Nested associated lists (e.g. Apartment → Photo)
|
|
59
|
+
|
|
60
|
+
- [x] Each nested row is `<turbo-frame id="{parent}_{id}_{assoc}_{child_id}">` with Turbo presentation links (same contract as top-level).
|
|
61
|
+
- [x] **`row_html_turbo_allowed?`:** `format.html` row open/close for **`not_accessible_through_html?`** models when **`params[:update]`** matches a nested associated row id (≥4 underscore segments, trailing numeric id).
|
|
62
|
+
- [x] Scalar field edit/cancel inside nested **`_show`** uses existing **`format.html`** field templates (no **`UnknownFormat`** for Photo).
|
|
63
|
+
|
|
64
|
+
### Field-level (`link_to_inline_edit` / `*_show` helpers)
|
|
65
|
+
|
|
66
|
+
- [x] **Stock `_show` scalar fields**: wrapped in `<turbo-frame id="{model}_{id}_{attribute}">`; `@inline_forms_turbo_field = true` so `link_to_inline_edit` omits `:remote => true`
|
|
67
|
+
- [x] **`link_to_inline_edit`**: uses Turbo when `@inline_forms_turbo_field` or explicit `turbo_frame: true`
|
|
68
|
+
- [x] **`InlineFormsController#edit`, `#update`, `#show`** (single attribute): `format.html` + `field_edit` / `field_show` templates when `turbo_frame_request?`
|
|
69
|
+
- [x] **`_edit.html.erb`**: omits `:remote => true` when `@turbo_frame` (Turbo form submit inside frame)
|
|
70
|
+
- [x] Remove `edit.js.erb`, `update.js.erb`, `show_element.js.erb`
|
|
71
|
+
- [x] **`not_accessible_through_html?` models** (e.g. Photo): nested list row **`show` / `close`** use **`format.html`** when **`params[:update]`** is a nested row id (`row_html_turbo_allowed?`); field **`edit` / `update` / `show`** already always register **`format.html`**.
|
|
72
|
+
|
|
73
|
+
### Widget re-init after swap
|
|
74
|
+
|
|
75
|
+
- [x] ActionText/Trix: `turbo:frame-load` in `inline_forms.js` attaches Trix editors after frame replace
|
|
76
|
+
- [x] jQuery UI datepicker / timepicker: re-bound on `turbo:frame-load` (autocomplete widgets in field partials still use inline scripts until Step 5)
|
|
77
|
+
|
|
78
|
+
### Helpers (`app/helpers/inline_forms_helper.rb`)
|
|
79
|
+
|
|
80
|
+
- [x] `close_link`, `link_to_soft_delete`, `link_to_destroy`, `link_to_new_record`, `link_to_versions_list`, `close_versions_list_link`: Turbo when `turbo_row:` (default **true**); legacy `remote: true` only when `turbo_row: false`
|
|
81
|
+
|
|
82
|
+
### Tests
|
|
83
|
+
|
|
84
|
+
- [x] Integration: stock row open/close on `/apartments` (Turbo frame + HTML `show` / `close`): `example_app_apartment_row_turbo_test.rb`
|
|
85
|
+
- [x] Integration: open apartment row → edit text field → save → cancel (field flow: `example_app_apartment_field_turbo_test.rb`)
|
|
86
|
+
- [x] Integration: nested Photo row open/close + name field cancel (`example_app_apartment_photos_pagination_test.rb`)
|
|
87
|
+
- [x] Integration: replace photo image (multipart) inside nested frame (`example_app_apartment_photos_pagination_test.rb`)
|
|
88
|
+
- [x] Integration: custom field-only page (`ApartmentsController#name_list`) — Turbo edit/update/cancel without full `_show`
|
|
89
|
+
- [x] Assert no `406 UnknownFormat` on Turbo field update (name list test)
|
|
90
|
+
- [x] Integration: row destroy + PaperTrail revert via Turbo (`example_app_apartment_row_turbo_test.rb`)
|
|
91
|
+
- [x] Integration: versions panel open/close via Turbo (`example_app_apartment_versions_turbo_test.rb`)
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Step 4 — Remaining UJS surfaces
|
|
96
|
+
|
|
97
|
+
### Lists and create flow
|
|
98
|
+
|
|
99
|
+
- [ ] Top-level index: wrap in `<turbo-frame id="apartments_list">`; in-frame pagination
|
|
100
|
+
- [ ] `new` / `create`: frame refresh or stream replacing list container
|
|
101
|
+
- [ ] Remove `new.js.erb`, `list.js.erb` (keep `format.js` only until Step 5 if needed for overlap)
|
|
102
|
+
- [ ] `_new.html.erb`: drop `:remote => true`
|
|
103
|
+
|
|
104
|
+
### Tree
|
|
105
|
+
|
|
106
|
+
- [ ] `_tree.html.erb`: same frame pattern as `_list`; remote pagination → frame pagination
|
|
107
|
+
- [ ] Remove tree-related UJS if any dedicated `*.js.erb` remain
|
|
108
|
+
|
|
109
|
+
### Versions panel
|
|
110
|
+
|
|
111
|
+
- [ ] `VersionsConcern#list_versions`: HTML frame / stream
|
|
112
|
+
- [ ] Remove `versions.js.erb`, `versions_list.js.erb`
|
|
113
|
+
- [ ] `_versions_list.html.erb`: drop `:remote => true` on revert links (or stream revert)
|
|
114
|
+
|
|
115
|
+
### Geo / misc
|
|
116
|
+
|
|
117
|
+
- [ ] `geo_code_curacao/list_streets.js.erb`: frame or stream for street dropdown
|
|
118
|
+
|
|
119
|
+
### Controller cleanup
|
|
120
|
+
|
|
121
|
+
- [ ] Every action in `InlineFormsController` + `VersionsConcern` has a non-JS response path
|
|
122
|
+
- [ ] Audit `respond_to` blocks: parallel `format.turbo_stream` where stream is cleaner than full frame
|
|
123
|
+
|
|
124
|
+
### Tests
|
|
125
|
+
|
|
126
|
+
- [ ] Top-level list pagination in frame
|
|
127
|
+
- [ ] Create apartment → list frame updates
|
|
128
|
+
- [ ] Versions panel open/close
|
|
129
|
+
- [ ] Assert zero `data-remote="true"` in rendered HTML for inline_forms flows
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Step 5 — Enable Drive, remove UJS
|
|
134
|
+
|
|
135
|
+
- [ ] Remove `Turbo.session.drive = false` from both layouts
|
|
136
|
+
- [ ] Delete `//= require jquery_ujs` from `inline_forms.js`
|
|
137
|
+
- [ ] Delete `//= require jquery.remotipart` from `inline_forms.js`
|
|
138
|
+
- [ ] Delete all `app/views/inline_forms/*.js.erb` (and geo `list_streets.js.erb`)
|
|
139
|
+
- [ ] Remove `format.js` branches from controllers (or empty stubs, then delete)
|
|
140
|
+
- [ ] Update `example_app_turbo_layout_test.rb`: Drive enabled (or line removed)
|
|
141
|
+
- [ ] Full `bundle exec rails test` in `--example` app
|
|
142
|
+
|
|
143
|
+
### jQuery (optional follow-up — not required to drop UJS)
|
|
144
|
+
|
|
145
|
+
These can remain while UJS is gone; separate migration if desired:
|
|
146
|
+
|
|
147
|
+
- [ ] `jquery.ui.all` (datepicker)
|
|
148
|
+
- [ ] `jquery.timepicker.js`
|
|
149
|
+
- [ ] `autocomplete-rails`
|
|
150
|
+
- [ ] `foundation` jQuery plugin → consider Foundation JS without jQuery or Stimulus
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Reference — UJS inventory
|
|
155
|
+
|
|
156
|
+
### `*.js.erb` (all to delete by Step 5)
|
|
157
|
+
|
|
158
|
+
| File | Effect |
|
|
159
|
+
|------|--------|
|
|
160
|
+
| `show.js.erb` | Replace row with `_show` |
|
|
161
|
+
| `edit.js.erb` | Replace `#update_span` with `_edit` form |
|
|
162
|
+
| `update.js.erb` | Replace with `{form_element}_show` |
|
|
163
|
+
| `show_element.js.erb` | Single-field show after update |
|
|
164
|
+
| `new.js.erb` | Replace with `_new` form |
|
|
165
|
+
| `list.js.erb` | Replace list container |
|
|
166
|
+
| `close.js.erb` | Fade + `_close` partial |
|
|
167
|
+
| `record_destroyed.js.erb` | Fade out row |
|
|
168
|
+
| `show_undo.js.erb` | Undo link after destroy |
|
|
169
|
+
| `versions.js.erb` | Fade + versions partial |
|
|
170
|
+
| `versions_list.js.erb` | Versions table |
|
|
171
|
+
| `geo_code_curacao/list_streets.js.erb` | Street list dropdown |
|
|
172
|
+
|
|
173
|
+
### Controller actions still on `format.js`
|
|
174
|
+
|
|
175
|
+
`index`, `show`, `edit`, `update`, `new`, `create`, `soft_delete`, `soft_restore`, `destroy`, `revert`, `list_versions`
|
|
176
|
+
|
|
177
|
+
### Key files
|
|
178
|
+
|
|
179
|
+
- `app/controllers/inline_forms_controller.rb`
|
|
180
|
+
- `app/controllers/concerns/versions_concern.rb`
|
|
181
|
+
- `app/views/inline_forms/_list.html.erb`, `_show.html.erb`, `_edit.html.erb`, `_new.html.erb`
|
|
182
|
+
- `app/helpers/inline_forms_helper.rb`
|
|
183
|
+
- `app/helpers/form_elements/*.rb` (`*_show` → `link_to_inline_edit`)
|
|
184
|
+
- `app/assets/javascripts/inline_forms/inline_forms.js`
|
|
185
|
+
- `app/views/layouts/inline_forms.html.erb`
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Custom field-only pages (helper bypass)
|
|
190
|
+
|
|
191
|
+
Stock `_show` / `_list` are not required for inline edit. Any page can call form-element helpers (e.g. `text_field_show(apartment, :name)`) inside a container with id `apartment_<id>_name`. Edit/update still hit `ApartmentsController#edit` / `#update` via polymorphic paths.
|
|
192
|
+
|
|
193
|
+
Example app **`--example` name list** (`GET /apartments/name_list`): custom page using the **same** turbo-field contract as stock `_show` (not a separate code path). Linked from the **More** menu; regression-tested after stock field Turbo lands.
|
data/lib/generators/USAGE
CHANGED
|
@@ -3,7 +3,7 @@ Description:
|
|
|
3
3
|
We will generate a model, a migration, a controller and a route. Please do check the migration and the model because they will likely need tweaking.
|
|
4
4
|
|
|
5
5
|
Example:
|
|
6
|
-
rails generate inline_forms Thing name:text_field description:
|
|
6
|
+
rails generate inline_forms Thing name:text_field description:plain_text yesno:check_box gender:boolean_with_values
|
|
7
7
|
|
|
8
8
|
This will create:
|
|
9
9
|
create app/models/thing.rb
|
|
@@ -19,7 +19,7 @@ This will create:
|
|
|
19
19
|
def inline_forms_field_list
|
|
20
20
|
[
|
|
21
21
|
[ :name, 'name', :text_field ],
|
|
22
|
-
[ :description, 'description', :
|
|
22
|
+
[ :description, 'description', :plain_text ],
|
|
23
23
|
[ :yesno, 'yesno', :check_box ],
|
|
24
24
|
[ :gender, 'gender', :boolean_with_values ],
|
|
25
25
|
]
|
|
@@ -223,6 +223,14 @@ select:hover, select:focus {
|
|
|
223
223
|
font-weight: bold;
|
|
224
224
|
font-size: 110%;
|
|
225
225
|
}
|
|
226
|
+
// Custom elements default to `display: inline`, which collapses the row layout
|
|
227
|
+
// of an empty/just-mounted top-level `<turbo-frame id="apartments_list">` and
|
|
228
|
+
// hides its rows under the fixed top bar. Force block layout so frames behave
|
|
229
|
+
// like the legacy `<div class="list_container">` and `<div class="row">` they
|
|
230
|
+
// replaced (see app/views/inline_forms/_list.html.erb).
|
|
231
|
+
turbo-frame {
|
|
232
|
+
display: block;
|
|
233
|
+
}
|
|
226
234
|
.list_container {
|
|
227
235
|
.row {
|
|
228
236
|
font-size: 1.2rem;
|
|
@@ -258,6 +266,30 @@ select:hover, select:focus {
|
|
|
258
266
|
}
|
|
259
267
|
}
|
|
260
268
|
|
|
269
|
+
// Field edit cancel: outer <a> carries Turbo/UJS; inner input[type=button] matches ok height.
|
|
270
|
+
.edit_form .row.collapse {
|
|
271
|
+
input[type="submit"].postfix.button,
|
|
272
|
+
a.inline_forms-field-cancel input[type="button"].postfix.button {
|
|
273
|
+
margin: 2px 0 !important;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.edit_form a.inline_forms-field-cancel {
|
|
278
|
+
display: inline-block;
|
|
279
|
+
padding: 0;
|
|
280
|
+
border: 0;
|
|
281
|
+
background: transparent;
|
|
282
|
+
line-height: 0;
|
|
283
|
+
vertical-align: top;
|
|
284
|
+
text-decoration: none;
|
|
285
|
+
|
|
286
|
+
input[type="button"] {
|
|
287
|
+
pointer-events: none;
|
|
288
|
+
cursor: pointer;
|
|
289
|
+
width: auto;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
261
293
|
.object_presentation {
|
|
262
294
|
background-color: #B94C32;
|
|
263
295
|
color: white;
|
|
@@ -434,22 +466,6 @@ select:hover, select:focus {
|
|
|
434
466
|
margin-bottom: 0.5em;
|
|
435
467
|
}
|
|
436
468
|
|
|
437
|
-
.ckeditor_area {
|
|
438
|
-
position: relative;
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
.ckeditor_area .glass_plate {
|
|
442
|
-
position: absolute;
|
|
443
|
-
top: -1px;
|
|
444
|
-
width: 98%;
|
|
445
|
-
height: 232px;
|
|
446
|
-
border: 0;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
.ckeditor_area .cke_top, .ckeditor_area .cke_bottom, .ckeditor_area .cke_border {
|
|
450
|
-
display: none;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
469
|
/* jQuery ui Slider 8 */
|
|
454
470
|
.slider {
|
|
455
471
|
width: 300px;
|
data/lib/inline_forms/version.rb
CHANGED