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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +176 -0
  3. data/README.rdoc +4 -2
  4. data/app/assets/javascripts/inline_forms/inline_forms.js +23 -0
  5. data/app/assets/stylesheets/inline_forms/inline_forms.scss +32 -16
  6. data/app/controllers/concerns/versions_concern.rb +2 -1
  7. data/app/controllers/inline_forms_application_controller.rb +5 -1
  8. data/app/controllers/inline_forms_controller.rb +120 -24
  9. data/app/helpers/form_elements/ckeditor.rb +4 -30
  10. data/app/helpers/form_elements/plain_text.rb +23 -0
  11. data/app/helpers/form_elements/plain_text_area.rb +7 -3
  12. data/app/helpers/form_elements/text_area.rb +4 -44
  13. data/app/helpers/form_elements/text_area_without_ckeditor.rb +5 -4
  14. data/app/helpers/form_elements/text_field.rb +2 -2
  15. data/app/helpers/inline_forms_helper.rb +127 -71
  16. data/app/views/devise/sessions/_form.html.erb +4 -1
  17. data/app/views/inline_forms/_close.html.erb +6 -4
  18. data/app/views/inline_forms/_edit.html.erb +7 -40
  19. data/app/views/inline_forms/_list.html.erb +52 -39
  20. data/app/views/inline_forms/_new.html.erb +23 -11
  21. data/app/views/inline_forms/_show.html.erb +13 -11
  22. data/app/views/inline_forms/_versions_list.html.erb +4 -8
  23. data/app/views/inline_forms/create_list_frame.html.erb +3 -0
  24. data/app/views/inline_forms/field_edit.html.erb +3 -0
  25. data/app/views/inline_forms/field_show.html.erb +3 -0
  26. data/app/views/inline_forms/new_record.html.erb +3 -0
  27. data/app/views/inline_forms/row_close.html.erb +5 -0
  28. data/app/views/inline_forms/row_destroyed.html.erb +9 -0
  29. data/app/views/inline_forms/row_show.html.erb +3 -0
  30. data/app/views/inline_forms/versions_list_panel.html.erb +3 -0
  31. data/app/views/inline_forms/versions_panel.html.erb +3 -0
  32. data/app/views/layouts/application.html.erb +0 -1
  33. data/app/views/layouts/inline_forms.html.erb +10 -1
  34. data/bin/inline_forms +22 -1
  35. data/bin/inline_forms_installer_core.rb +38 -3
  36. data/docs/ujs-to-turbo.md +193 -0
  37. data/lib/generators/USAGE +2 -2
  38. data/lib/generators/assets/stylesheets/inline_forms.scss +32 -16
  39. data/lib/inline_forms/version.rb +1 -1
  40. data/lib/inline_forms.rb +58 -2
  41. data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_field_turbo_test.rb +74 -0
  42. data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_name_list_test.rb +73 -0
  43. data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_photos_pagination_test.rb +199 -15
  44. data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_row_turbo_test.rb +94 -0
  45. data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_top_level_new_test.rb +100 -0
  46. data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_versions_turbo_test.rb +27 -0
  47. data/lib/installer_templates/example_app_tests/test/models/example_app_plain_text_rich_text_edge_cases_test.rb +46 -0
  48. data/lib/installer_templates/example_app_views/apartments/name_list.html.erb +26 -0
  49. data/lib/installer_templates/example_app_views/inline_forms/_header.html.erb +45 -0
  50. data/test/inline_forms_generator_test.rb +10 -0
  51. data/test/plain_text_configuration_test.rb +90 -0
  52. metadata +21 -5
  53. data/app/views/inline_forms/edit.js.erb +0 -1
  54. data/app/views/inline_forms/show_element.js.erb +0 -1
  55. data/app/views/inline_forms/update.js.erb +0 -1
  56. 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
- <% flash.each do |key, value| %>
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
- <%= form_tag polymorphic_path(@Klass, :update => @update_span,
29
- :parent_class => @parent_class,
30
- :parent_id => @parent_id ),
31
- :multipart => true, :remote => true, :class => "edit_form" do -%>
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
  &nbsp;
57
68
  </div>
58
69
  <div class='small-11 column' >
59
- <%= link_to( polymorphic_path(@Klass, :update => @update_span,
60
- :parent_class => @parent_class,
61
- :parent_id => @parent_id,
62
- :ul_needed => true ),
63
- :remote => true,
64
- ) do -%>
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
  &nbsp;
69
71
  </div>
70
- <div id="<%= css_class_id -%>" class='small-11 column'>
72
+ <turbo-frame id="<%= css_class_id -%>" class="small-11 column">
71
73
  <% else %>
72
- <div id="<%= css_class_id -%>" class='small-12 column' >
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
- </div>
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
  &nbsp;
86
88
  </div>
87
- <div id="<%= css_class_id -%>" class='small-11 column' >
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
- </div>
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
- <span id="<%= css_class_id -%>" >
109
+ <turbo-frame id="<%= css_class_id -%>">
108
110
  <%= send("#{form_element}_show", @object, attribute) -%>
109
- </span>
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
- <div id="<%= css_class_id -%>">
121
- <%= render 'versions' %>
122
- </div>
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
- <%= link_to t('inline_forms.view.restore'),
50
- send('revert_' + @object.class.to_s.underscore + "_path",
51
- version,
52
- :update => "#{@object.class.name.underscore}_#{@object.id}"
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
  &nbsp;
59
55
  <% end %>
@@ -0,0 +1,3 @@
1
+ <turbo-frame id="<%= @update_span %>">
2
+ <%= render partial: "inline_forms/list" %>
3
+ </turbo-frame>
@@ -0,0 +1,3 @@
1
+ <turbo-frame id="<%= @update_span %>">
2
+ <%= render partial: "inline_forms/edit" %>
3
+ </turbo-frame>
@@ -0,0 +1,3 @@
1
+ <turbo-frame id="<%= @update_span %>">
2
+ <%= inline_forms_field_show(@object, @attribute, @form_element, turbo_frame: @turbo_field_show_turbo_frame) %>
3
+ </turbo-frame>
@@ -0,0 +1,3 @@
1
+ <turbo-frame id="<%= @update_span %>">
2
+ <%= render partial: "inline_forms/new" %>
3
+ </turbo-frame>
@@ -0,0 +1,5 @@
1
+ <turbo-frame id="<%= @update_span %>">
2
+ <div class="row odd top-level">
3
+ <%= render partial: "inline_forms/close" %>
4
+ </div>
5
+ </turbo-frame>
@@ -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>
@@ -0,0 +1,3 @@
1
+ <turbo-frame id="<%= @update_span %>">
2
+ <%= render partial: "inline_forms/show" %>
3
+ </turbo-frame>
@@ -0,0 +1,3 @@
1
+ <turbo-frame id="<%= @update_span %>">
2
+ <%= render partial: "inline_forms/versions_list" %>
3
+ </turbo-frame>
@@ -0,0 +1,3 @@
1
+ <turbo-frame id="<%= @update_span %>">
2
+ <%= render partial: "inline_forms/versions" %>
3
+ </turbo-frame>
@@ -24,6 +24,5 @@
24
24
  window.Turbo = Turbo
25
25
  </script>
26
26
  <%= javascript_include_tag "https://unpkg.com/trix@1.3.1/dist/trix.js" %>
27
- <%= javascript_include_tag Ckeditor.cdn_url if defined?(Ckeditor) %>
28
27
  </body>
29
28
  </html>
@@ -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
- if ! run("rails new #{app_name} -m #{app_template_file} --skip-bundle --skip-bootsnap --javascript=importmap")
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-i18n', :git => 'https://github.com/acesuares/devise-i18n.git'
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
- # TODO: fix text_area into text_area_without_ckeditor
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:text_area yesno:check_box gender:boolean_with_values
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', :text_area ],
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;
@@ -1,4 +1,4 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
  module InlineForms
3
- VERSION = "7.2.11"
3
+ VERSION = "7.5.2"
4
4
  end