inline_forms 8.1.57 → 8.1.59
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 +56 -0
- data/app/assets/stylesheets/inline_forms/_theme.scss +13 -0
- data/app/assets/stylesheets/inline_forms/devise.scss +4 -1
- data/app/assets/stylesheets/inline_forms/foundation_and_overrides.scss +1 -0
- data/app/assets/stylesheets/inline_forms/inline_forms.scss +35 -16
- data/app/controllers/inline_forms_controller.rb +1 -1
- data/lib/generators/templates/application_record.rb +9 -0
- data/lib/inline_forms/dropdown_order.rb +61 -0
- data/lib/inline_forms/form_elements/check_list_helper.rb +1 -1
- data/lib/inline_forms/form_elements/dropdown_helper.rb +1 -1
- data/lib/inline_forms/form_elements/dropdown_with_other_helper.rb +1 -0
- data/lib/inline_forms/version.rb +1 -1
- data/lib/inline_forms.rb +9 -0
- data/lib/locales/inline_forms.en.yml +1 -0
- data/lib/locales/inline_forms.nl.yml +1 -0
- data/test/dummy/app/models/part.rb +1 -0
- data/test/dummy/app/models/phase.rb +9 -0
- data/test/integration/dropdown_test.rb +104 -0
- data/test/integration/locale_test.rb +19 -1
- data/test/integration_test_helper.rb +9 -0
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 122426ec8aafea8e48c9693153e155891b9f97cb73a40983a0bdcf21051acc26
|
|
4
|
+
data.tar.gz: bbf2a4dacbfa14d9cf84cab79de9356f5623542e28029aa18f000fe25c21b432
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 043a09c0faaedf4d13ecc8ea20cf6b772709fc20237c7a6e87da188a2392045fab619a7b589a2966ae7f9fa49bc27e5f1caa37778b86605842595ccd74c4f56d
|
|
7
|
+
data.tar.gz: eaa601424c02655fdec7721a81f5f48af2674d58077e2325f2c1ccab49375d60f1dea794891a86dd7d85e7e3c3b2cebb8fad26fedaa2cded1fe2326f65fd9a27
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,62 @@ All notable changes to this project are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [8.1.59] - 2026-10-22
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **Dropdowns follow the model's order again, with one rule for every table-backed list.** 8.1.58 made `dropdown` sort its options alphabetically by presentation. That overrode the order lookups depend on, such as '---' / 'onbekend' first and process phases in process order. Before that, from 2017 to 8.1.57, the sort was discarded and dropdowns showed database order. Until 2015 they used the model's `order_by_clause`. `dropdown_with_other` used database order, and `check_list` used `inline_forms_list`. All three now use `InlineForms::DropdownOrder`; the first match wins:
|
|
12
|
+
1. the model declares **`inline_forms_dropdown_order :column`** (or `:column, :desc`);
|
|
13
|
+
2. the table has a **`dropdown_order`** column, used ascending: give '---' 0 and the phases 10, 20, 30;
|
|
14
|
+
3. the model's `inline_forms_list` scope (its list order; unchanged for `check_list`);
|
|
15
|
+
4. database order, including a `default_scope` order.
|
|
16
|
+
|
|
17
|
+
Rules 1 and 2 replace any `default_scope` order, put NULLs last (a record nobody has numbered yet goes to the end) and break ties on `id`.
|
|
18
|
+
- The macro is on every Active Record model (engine initializer `inline_forms.dropdown_order`), so hosts need no `ApplicationRecord` change. An unknown direction raises `ArgumentError` when the class loads; an unknown column raises when the dropdown renders.
|
|
19
|
+
- **Hosts:** lookups that declare nothing keep their `inline_forms_list` order. StProject's lookups declare `order(:name, :id)`, so they stay alphabetical; Scouting's `default_scope { order(name: :asc) }` still applies. A lookup that needs a fixed order gets a `dropdown_order` integer column, or `inline_forms_dropdown_order`. Papiamentu's `def self.order_by_clause` is still not read; use the macro instead.
|
|
20
|
+
- The installer's `ApplicationRecord` template documents the rule.
|
|
21
|
+
- **Example app: a `Phase` lookup shows the rule.** `inline_forms create … --example` generates `Phase` (`name`, `dropdown_order`), gives Apartment a `phase` dropdown below `owner`, and seeds `---` 0, helprequest 10, decision 20, admittance 30, created in neither order. On an apartment the dropdown reads ---, helprequest, decision, admittance, while `/phases` lists them alphabetically (`_list_order:name`). The seeded apartments get helprequest, decision and ---.
|
|
22
|
+
|
|
23
|
+
### Tests
|
|
24
|
+
|
|
25
|
+
- `dropdown_test`: each rule, desc, declared column over `dropdown_order`, NULLs last, `id` tie-break, `dropdown_with_other` and `check_list`, bad direction or column. The dummy app gains a `Phase` lookup with a `dropdown_order` column (`Part belongs_to :phase`).
|
|
26
|
+
- Example app: `example_app_phase_dropdown_test` checks the apartment's phase dropdown order, that choosing a phase saves it, and that `/phases` keeps its alphabetical list order.
|
|
27
|
+
|
|
28
|
+
### Lockstep
|
|
29
|
+
|
|
30
|
+
- validation_hints 8.1.59, inline_forms_installer 8.1.59, inline_forms_schema_edit 8.1.59.
|
|
31
|
+
|
|
32
|
+
## [8.1.58] - 2026-10-21
|
|
33
|
+
|
|
34
|
+
Three gem issues found in the StProject review (2026-10-04). The fourth it listed, the CSS `@import` of `http://fonts.googleapis.com`, was already fixed in 8.1.22 (self-hosted Open Sans). Also the first step of the Sass `@import` → `@use` move.
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- **Sass: the entry points load `foundation_and_overrides` with `@use`.** `inline_forms/inline_forms.scss` and `inline_forms/devise.scss` replace `@import 'foundation_and_overrides'` with `@use 'foundation_and_overrides' as *` plus `@forward 'foundation_and_overrides'`. Dart Sass 3 removes `@import`, and these were two of the five gem `@import`s a host build reported.
|
|
39
|
+
- The compiled `inline_forms.css` and `devise.css` are **byte-identical** to 8.1.57's (compressed, compiled with the host's dartsass-rails command; same SHA-256).
|
|
40
|
+
- One rule needed spelling out to stay identical: Foundation's flex grid makes `.columns` an alias with `.columns { @extend .column }`, which reached the gem's `.column { padding-right: 0 !important }` only while the file was `@import`ed. That rule is now `.column, .columns`.
|
|
41
|
+
- The `@forward` keeps the module's API for hosts: Foundation's members (`rem-calc`, `$global-width`, ...) stay visible through `@use "inline_forms/inline_forms" as *`, and the `!default` `$if-*` palette can still be set with `@use "inline_forms/inline_forms" with ($if-accent: ...)`. Without it, both fail to compile.
|
|
42
|
+
- The URL is relative, so it resolves next to the gem's file first, as the `@import` did; a host's `app/assets/stylesheets/inline_forms/foundation_and_overrides.scss` or anything in `app/assets/builds` cannot shadow it.
|
|
43
|
+
- Deprecations per entry point (with `--verbose`): 371 → 370 (`[import]` 118 → 117, `[if-function]` 58, `[global-builtin]` 195). Without `--verbose` Sass prints 5 per category and "356 → 355 repetitive deprecation warnings omitted".
|
|
44
|
+
- **Still `@import`, inside `foundation_and_overrides.scss`:** `settings/settings` and `foundation` (foundation-rails 6.9 is `@import`-only and its settings globals feed the overrides), and `theme`. A `@use 'theme'` would have to precede the settings `@import`, which moves the theme's CSS ahead of Foundation's and runs before `$body-background` exists; `meta.load-css` keeps the position but hides the `$if-*` palette from hosts. All three wait for the Foundation decision. The remaining 114 `[import]`, and all `[if-function]` / `[global-builtin]` warnings, are inside foundation-rails. The deadline is a dartsass-rails release that allows sass-embedded ≥ 3 (0.5.1 pins `~> 1.63`), not a date. No `--quiet-deps` / `--silence-deprecation` was added.
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
|
|
48
|
+
- **`dropdown` options are sorted again.** `dropdown_edit` called `values.sort_by { _presentation }` and discarded the result, so the options came out in id order. They are now sorted by their (dropdown) presentation.
|
|
49
|
+
- **The failed-create flash header is translated.** `InlineFormsController#create` set `flash[:header]` to the hard-coded Dutch `"Kan <class_name> niet aanmaken."` (with the underscored class name, e.g. `client`) in every locale. It now uses the new key `inline_forms.view.create_failed` with the model's human name: "Could not create %{model}." (en), "Kan %{model} niet aanmaken." (nl). A host with its own locale needs that key.
|
|
50
|
+
- **"+" and close icons on the gold bands meet WCAG contrast.** `.new_button` / `.close_button` in the model top bar and the associated header were the accent colour on gold: 2.9:1 on the model top bar (below the 3:1 for icons), and white on hover (1.8:1). They now use two new variables, `--if-color-on-band` and `--if-color-on-band-hover` (Sass `$if-on-band` / `$if-on-band-hover`), which clear 4.5:1 on both bands in every preset theme (default, dark, sepia, high-contrast). A host that overrides `--if-color-secondary` / `--if-color-header-band` should set them too. The associated header's close-button hover rule also targeted `.new_button:focus` instead of `.close_button:focus`.
|
|
51
|
+
- **Installer: generated apps compile the gem's SCSS on every Dart Sass build.** The `config/initializers/inline_forms_dartsass_builds.rb` template maps `inline_forms_install/{inline_forms,devise}_main.scss` (`@use "inline_forms/inline_forms" as *`) to `app/assets/builds/inline_forms/{inline_forms,devise}.css`. dartsass-rails passes `app/assets/stylesheets` and then every `config.assets.paths` entry as load paths, and `app/assets/builds` comes before the gem, so from the second build on the `@use` resolved to the app's previous compiled `.css` instead of the gem's `.scss`. Gem stylesheet changes stopped reaching an app after its first build (and the example-app gate did not test them). The template now adds `--load-path=<inline_forms>/app/assets/stylesheets` to `config.dartsass.build_options`, which precede the load paths, as StProject's own initializer already does. **Existing apps** generated by an earlier installer should add the same two lines to their initializer.
|
|
52
|
+
|
|
53
|
+
### Tests
|
|
54
|
+
|
|
55
|
+
- `dropdown_test` (options sorted by presentation), `locale_test` (failure header in en and nl, `inline_forms.view.create_failed` in both locale files).
|
|
56
|
+
- Example app: `example_app_sass_deprecations_test` compiles both gem entry points with sass-embedded (verbose) and fails if the gem gains an `@import` beyond the three above, or an entry point has more than 117 `[import]` deprecations.
|
|
57
|
+
- Example app: `example_app_dartsass_build_test` checks that the gem's stylesheet dir is the first load path in the dartsass command, and that `app/assets/builds/inline_forms/{inline_forms,devise}.css` match a direct compile of the gem's entry points (test_helper's `dartsass:build` runs after the installer's, so builds/ already held a previous build).
|
|
58
|
+
|
|
59
|
+
### Lockstep
|
|
60
|
+
|
|
61
|
+
- validation_hints 8.1.58, inline_forms_installer 8.1.58, inline_forms_schema_edit 8.1.58.
|
|
62
|
+
|
|
7
63
|
## [8.1.57] - 2026-10-05
|
|
8
64
|
|
|
9
65
|
Fixes from a security/UI review of a host app (StProject), plus missing and hard-coded translations found when StProject switched to `:nl` with `raise_on_missing_translations = :strict`.
|
|
@@ -33,6 +33,11 @@ $if-flash-accent: #fade7a !default; // flash border band
|
|
|
33
33
|
$if-error-bg: #a70f0f !default;
|
|
34
34
|
$if-success-bg: #4f8d0d !default;
|
|
35
35
|
$if-on-dark: #FFFFFF !default; // text/icons on primary/secondary bands
|
|
36
|
+
// The "+" / close icons on the gold bands (secondary, header-band). The
|
|
37
|
+
// accent was 2.9:1 on the secondary gold and the white hover 1.8:1; these
|
|
38
|
+
// clear 4.5:1 on both bands.
|
|
39
|
+
$if-on-band: #8F2F18 !default;
|
|
40
|
+
$if-on-band-hover: #4A1A0C !default;
|
|
36
41
|
|
|
37
42
|
:root {
|
|
38
43
|
--if-color-primary: #{$if-primary};
|
|
@@ -48,6 +53,8 @@ $if-on-dark: #FFFFFF !default; // text/icons on primary/secondary bands
|
|
|
48
53
|
--if-color-error-bg: #{$if-error-bg};
|
|
49
54
|
--if-color-success-bg: #{$if-success-bg};
|
|
50
55
|
--if-color-on-dark: #{$if-on-dark};
|
|
56
|
+
--if-color-on-band: #{$if-on-band};
|
|
57
|
+
--if-color-on-band-hover: #{$if-on-band-hover};
|
|
51
58
|
// Foundation globals, bridged so body/anchors can be re-themed too.
|
|
52
59
|
--if-color-body-bg: #{$body-background};
|
|
53
60
|
--if-color-body-text: #{$body-font-color};
|
|
@@ -85,6 +92,8 @@ body.theme-dark {
|
|
|
85
92
|
--if-color-input-bg-hover: #332e25;
|
|
86
93
|
--if-color-input-bg-focus: #3a352b;
|
|
87
94
|
--if-color-flash-accent: #7a6829;
|
|
95
|
+
--if-color-on-band: #fdeee5;
|
|
96
|
+
--if-color-on-band-hover: #ffffff;
|
|
88
97
|
--if-color-body-bg: #1c1a16;
|
|
89
98
|
--if-color-body-text: #e8e4da;
|
|
90
99
|
--if-color-anchor: #e0a179;
|
|
@@ -108,6 +117,8 @@ body.theme-sepia {
|
|
|
108
117
|
--if-color-input-bg-hover: #fffaf0;
|
|
109
118
|
--if-color-input-bg-focus: #fffdf7;
|
|
110
119
|
--if-color-flash-accent: #dcc078;
|
|
120
|
+
--if-color-on-band: #6e3a20;
|
|
121
|
+
--if-color-on-band-hover: #3a2418;
|
|
111
122
|
--if-color-body-bg: #f4ecd8;
|
|
112
123
|
--if-color-body-text: #5b4636;
|
|
113
124
|
--if-color-anchor: #8b5e3c;
|
|
@@ -124,6 +135,8 @@ body.theme-high-contrast {
|
|
|
124
135
|
--if-color-input-bg-hover: #f5f5f5;
|
|
125
136
|
--if-color-input-bg-focus: #ffffff;
|
|
126
137
|
--if-color-flash-accent: #ffd500;
|
|
138
|
+
--if-color-on-band: #b30000;
|
|
139
|
+
--if-color-on-band-hover: #000000;
|
|
127
140
|
--if-color-body-bg: #ffffff;
|
|
128
141
|
--if-color-body-text: #000000;
|
|
129
142
|
--if-color-anchor: #0000d6;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// Open Sans @font-face lives in inline_forms_fonts.css.erb (self-hosted,
|
|
2
2
|
// Sprockets-served; linked from the layouts) — no external font CDN.
|
|
3
|
-
@
|
|
3
|
+
// @use / @forward of foundation_and_overrides: see the module-system note
|
|
4
|
+
// in inline_forms.scss.
|
|
5
|
+
@forward 'foundation_and_overrides';
|
|
6
|
+
@use 'foundation_and_overrides' as *;
|
|
4
7
|
|
|
5
8
|
// Devise pages have only `.contain-to-grid` as the top-bar wrapper (no
|
|
6
9
|
// `.fixed` variant), so constrain it directly to the F5 1000px row
|
|
@@ -55,6 +55,7 @@ $body-font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Roboto, Arial, sans
|
|
|
55
55
|
|
|
56
56
|
// Theme bridge: palette as CSS custom properties (must come after
|
|
57
57
|
// 'foundation' so it can read $body-background / $anchor-color).
|
|
58
|
+
// Stays @import: see the module-system note in inline_forms.scss.
|
|
58
59
|
@import 'theme';
|
|
59
60
|
|
|
60
61
|
@import '../../../vendor/assets/stylesheets/tippy.css';
|
|
@@ -2,19 +2,34 @@
|
|
|
2
2
|
// Open Sans @font-face lives in inline_forms_fonts.css.erb (self-hosted,
|
|
3
3
|
// Sprockets-served; linked from the layouts) — no external font CDN.
|
|
4
4
|
//
|
|
5
|
-
// Module-system status (8.1.
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
5
|
+
// Module-system status (8.1.58). Loaded with @use:
|
|
6
|
+
// - `foundation_icons` (8.1.24): a self-contained partial.
|
|
7
|
+
// - `foundation_and_overrides`, here and in devise.scss (8.1.58). The
|
|
8
|
+
// compiled CSS is byte-identical to the @import version (the `.columns`
|
|
9
|
+
// alias below is the one rule that needed spelling out). The relative URL
|
|
10
|
+
// resolves next to this file first, as the @import did, so a same-named
|
|
11
|
+
// host or app/assets/builds file cannot shadow it. The @forward keeps what
|
|
12
|
+
// hosts saw through `@use "inline_forms/inline_forms"` before: Foundation's
|
|
13
|
+
// members (rem-calc, $global-width, ...) and the !default `$if-*` palette,
|
|
14
|
+
// configurable with `with (...)`.
|
|
15
|
+
// Still @import, inside foundation_and_overrides:
|
|
16
|
+
// - 'settings/settings' and 'foundation': foundation-rails 6.9 is
|
|
17
|
+
// @import-only and its settings globals feed our overrides. Blocked by the
|
|
18
|
+
// Foundation decision (unmaintained upstream; no module API coming).
|
|
19
|
+
// - 'theme': a @use must precede the settings @import, which would move the
|
|
20
|
+
// theme's CSS ahead of Foundation's and run before $body-background exists;
|
|
21
|
+
// meta.load-css keeps the position but hides the `$if-*` palette from hosts
|
|
22
|
+
// (`with ($if-accent: ...)` stops working). Moves with Foundation.
|
|
23
|
+
// The deadline is a dartsass-rails release that allows sass-embedded >= 3
|
|
24
|
+
// (Dart Sass 3 removes @import; 0.5.1 pins ~> 1.63), not a date.
|
|
25
|
+
// See stuff/improvement-plan.md §1.
|
|
12
26
|
//
|
|
13
27
|
// jQuery UI SCSS (sunny theme + widget styles) was dropped in 8.1.26 along
|
|
14
28
|
// with the last jQuery UI widgets (native date/time/month/range/datalist
|
|
15
29
|
// inputs replaced them).
|
|
16
30
|
@use 'inline_forms/foundation_icons';
|
|
17
|
-
@
|
|
31
|
+
@forward 'foundation_and_overrides';
|
|
32
|
+
@use 'foundation_and_overrides' as *;
|
|
18
33
|
|
|
19
34
|
// Foundation 6 makes `.row` a flex container (`display: flex`); a nested
|
|
20
35
|
// `.row` is therefore a flex item and shrinks to its content's intrinsic
|
|
@@ -223,12 +238,12 @@ select:hover, select:focus {
|
|
|
223
238
|
}
|
|
224
239
|
.new_button {
|
|
225
240
|
background-color: var(--if-color-secondary);
|
|
226
|
-
color: var(--if-color-
|
|
241
|
+
color: var(--if-color-on-band);
|
|
227
242
|
font-size: 1.3rem;
|
|
228
243
|
padding: 0 2rem 0 1rem;
|
|
229
244
|
}
|
|
230
245
|
.new_button:hover, .new_button:focus {
|
|
231
|
-
color:
|
|
246
|
+
color: var(--if-color-on-band-hover);
|
|
232
247
|
-webkit-transition-property: color;
|
|
233
248
|
transition-property: color;
|
|
234
249
|
}
|
|
@@ -358,26 +373,26 @@ turbo-frame {
|
|
|
358
373
|
.new_button {
|
|
359
374
|
background-color: var(--if-color-header-band);
|
|
360
375
|
font-size: 1.5rem;
|
|
361
|
-
color: var(--if-color-
|
|
376
|
+
color: var(--if-color-on-band);
|
|
362
377
|
padding: 0 0.5rem 0 1rem;
|
|
363
378
|
margin: 0;
|
|
364
379
|
}
|
|
365
380
|
.new_button:hover, .new_button:focus {
|
|
366
381
|
background-color: var(--if-color-header-band);
|
|
367
|
-
color:
|
|
382
|
+
color: var(--if-color-on-band-hover);
|
|
368
383
|
-webkit-transition-property: color;
|
|
369
384
|
transition-property: color;
|
|
370
385
|
}
|
|
371
386
|
.close_button {
|
|
372
387
|
background-color: var(--if-color-header-band);
|
|
373
388
|
font-size: 1.5rem;
|
|
374
|
-
color: var(--if-color-
|
|
389
|
+
color: var(--if-color-on-band);
|
|
375
390
|
padding: 0 0.5rem 0 1rem;
|
|
376
391
|
margin: 0;
|
|
377
392
|
}
|
|
378
|
-
.close_button:hover, .
|
|
393
|
+
.close_button:hover, .close_button:focus {
|
|
379
394
|
background-color: var(--if-color-header-band);
|
|
380
|
-
color:
|
|
395
|
+
color: var(--if-color-on-band-hover);
|
|
381
396
|
-webkit-transition-property: color;
|
|
382
397
|
transition-property: color;
|
|
383
398
|
}
|
|
@@ -423,7 +438,11 @@ turbo-frame {
|
|
|
423
438
|
margin-bottom: 1.5em;
|
|
424
439
|
}
|
|
425
440
|
|
|
426
|
-
|
|
441
|
+
// `.columns` named explicitly: Foundation's flex grid aliases it with
|
|
442
|
+
// `.columns { @extend .column }`, which reached this rule while
|
|
443
|
+
// foundation_and_overrides was @imported. Since it is @used (8.1.58),
|
|
444
|
+
// that @extend no longer crosses into this module.
|
|
445
|
+
.column, .columns {
|
|
427
446
|
padding-right: 0 !important;
|
|
428
447
|
}
|
|
429
448
|
|
|
@@ -189,7 +189,7 @@ class InlineFormsController < ApplicationController
|
|
|
189
189
|
format.html { render_list_frame_after_save }
|
|
190
190
|
end
|
|
191
191
|
else
|
|
192
|
-
flash.now[:header] = [ "
|
|
192
|
+
flash.now[:header] = [ t("inline_forms.view.create_failed", model: @object.class.model_name.human) ]
|
|
193
193
|
flash.now[:error] = @object.errors.to_a
|
|
194
194
|
respond_to do |format|
|
|
195
195
|
@object.inline_forms_attribute_list = attributes
|
|
@@ -26,6 +26,15 @@ class ApplicationRecord < ActiveRecord::Base
|
|
|
26
26
|
# `unscope`/`reorder` cleanly.
|
|
27
27
|
scope :inline_forms_list, -> { all }
|
|
28
28
|
|
|
29
|
+
# Dropdowns, dropdown_with_other and check_list offer a model's records in
|
|
30
|
+
# this order (first match wins; InlineForms::DropdownOrder):
|
|
31
|
+
# 1. inline_forms_dropdown_order :col # or :col, :desc
|
|
32
|
+
# 2. a `dropdown_order` integer column, ascending (e.g. '---' 0,
|
|
33
|
+
# 'helprequest' 10, 'decision' 20, 'admittance' 30)
|
|
34
|
+
# 3. inline_forms_list above
|
|
35
|
+
# 4. database order
|
|
36
|
+
# 1 and 2 put NULLs last and break ties on id.
|
|
37
|
+
|
|
29
38
|
# Wrapper for @model.human_attribute_name -> Model.human_attribute_name
|
|
30
39
|
def human_attribute_name(*args)
|
|
31
40
|
self.class.human_attribute_name(*args)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module InlineForms
|
|
4
|
+
# The order in which a table-backed form element (dropdown,
|
|
5
|
+
# dropdown_with_other, check_list) offers its records. First match wins:
|
|
6
|
+
#
|
|
7
|
+
# 1. the model declares `inline_forms_dropdown_order :column, :asc|:desc`;
|
|
8
|
+
# 2. the table has a `dropdown_order` column (ascending), so a lookup can
|
|
9
|
+
# put '---' / 'onbekend' first and process phases in process order;
|
|
10
|
+
# 3. the model's `inline_forms_list` scope (its list order);
|
|
11
|
+
# 4. database order (a default_scope order included).
|
|
12
|
+
#
|
|
13
|
+
# 1 and 2 replace any default_scope order, put NULLs last (a record nobody
|
|
14
|
+
# numbered yet goes to the end) and break ties on the primary key.
|
|
15
|
+
#
|
|
16
|
+
# The macro is on every Active Record model (engine initializer), so hosts
|
|
17
|
+
# need no ApplicationRecord change.
|
|
18
|
+
module DropdownOrder
|
|
19
|
+
COLUMN = "dropdown_order"
|
|
20
|
+
DIRECTIONS = %i[asc desc].freeze
|
|
21
|
+
|
|
22
|
+
module ClassMethods
|
|
23
|
+
def inline_forms_dropdown_order(column, direction = :asc)
|
|
24
|
+
direction = direction.to_s.downcase.to_sym
|
|
25
|
+
unless DIRECTIONS.include?(direction)
|
|
26
|
+
raise ArgumentError, "inline_forms_dropdown_order: direction must be :asc or :desc, got #{direction.inspect}"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
@inline_forms_dropdown_order = [ column.to_s, direction ].freeze
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# [column, direction] declared on this class or an ancestor, or nil.
|
|
33
|
+
def inline_forms_dropdown_order_spec
|
|
34
|
+
return @inline_forms_dropdown_order if defined?(@inline_forms_dropdown_order)
|
|
35
|
+
|
|
36
|
+
superclass.inline_forms_dropdown_order_spec if superclass.respond_to?(:inline_forms_dropdown_order_spec)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.apply(relation)
|
|
41
|
+
klass = relation.klass
|
|
42
|
+
column, direction = klass.inline_forms_dropdown_order_spec if klass.respond_to?(:inline_forms_dropdown_order_spec)
|
|
43
|
+
column, direction = COLUMN, :asc if column.nil? && klass.column_names.include?(COLUMN)
|
|
44
|
+
|
|
45
|
+
if column
|
|
46
|
+
unless klass.column_names.include?(column)
|
|
47
|
+
raise ArgumentError, "#{klass.name}.inline_forms_dropdown_order: #{klass.table_name} has no column #{column.inspect}"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
table = klass.arel_table
|
|
51
|
+
keys = [ table[column].public_send(direction).nulls_last ]
|
|
52
|
+
keys << table[klass.primary_key].asc if klass.primary_key && klass.primary_key != column
|
|
53
|
+
relation.reorder(*keys)
|
|
54
|
+
elsif klass.respond_to?(:inline_forms_list)
|
|
55
|
+
relation.merge(klass.inline_forms_list)
|
|
56
|
+
else
|
|
57
|
+
relation
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -21,7 +21,7 @@ module InlineForms
|
|
|
21
21
|
object.send(attribute).build if object.send(attribute).empty?
|
|
22
22
|
klass = object.send(attribute).first.class
|
|
23
23
|
values = cancan_enabled? ? klass.accessible_by(current_ability) : klass.all
|
|
24
|
-
values =
|
|
24
|
+
values = InlineForms::DropdownOrder.apply(values)
|
|
25
25
|
out = ''
|
|
26
26
|
values.each do | item |
|
|
27
27
|
out << "<div class='row #{cycle('odd', 'even')}'>"
|
|
@@ -29,7 +29,7 @@ module InlineForms
|
|
|
29
29
|
if klass.method_defined? :disabled_for_dropdown?
|
|
30
30
|
options_disabled = values.map{|v| v.id if v.disabled_for_dropdown?}.compact
|
|
31
31
|
end
|
|
32
|
-
values
|
|
32
|
+
values = InlineForms::DropdownOrder.apply(values)
|
|
33
33
|
# the leading underscore is to avoid name conflicts, like 'email' and 'email_type' will result in 'email' and 'email[email_type_id]' in the form!
|
|
34
34
|
collection_select( ('_' + object.class.to_s.underscore).to_sym,
|
|
35
35
|
attribute.to_s.foreign_key.to_sym,
|
data/lib/inline_forms/version.rb
CHANGED
data/lib/inline_forms.rb
CHANGED
|
@@ -10,6 +10,7 @@ require "inline_forms/schema_apply"
|
|
|
10
10
|
require "inline_forms/schema_label"
|
|
11
11
|
require "inline_forms/form_element_from_callee"
|
|
12
12
|
require "inline_forms/searchable"
|
|
13
|
+
require "inline_forms/dropdown_order"
|
|
13
14
|
require "inline_forms/archived_form_elements"
|
|
14
15
|
require "inline_forms/form_element_registry"
|
|
15
16
|
require "inline_forms/form_elements"
|
|
@@ -359,6 +360,14 @@ module InlineForms
|
|
|
359
360
|
end
|
|
360
361
|
end
|
|
361
362
|
|
|
363
|
+
# `inline_forms_dropdown_order` on every model (InlineForms::DropdownOrder),
|
|
364
|
+
# so hosts need no ApplicationRecord change to declare a dropdown order.
|
|
365
|
+
initializer "inline_forms.dropdown_order" do
|
|
366
|
+
ActiveSupport.on_load(:active_record) do
|
|
367
|
+
extend InlineForms::DropdownOrder::ClassMethods
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
|
|
362
371
|
initializer "inline_forms.assets.precompile" do |app|
|
|
363
372
|
# Skip when the host has no Sprockets-style pipeline (e.g. the test/dummy
|
|
364
373
|
# harness); generated apps always bundle sprockets-rails.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../integration_test_helper"
|
|
4
|
+
|
|
5
|
+
# Order of the records table-backed form elements offer
|
|
6
|
+
# (InlineForms::DropdownOrder): the model's inline_forms_dropdown_order, else
|
|
7
|
+
# a dropdown_order column, else inline_forms_list, else database order.
|
|
8
|
+
class DropdownTest < InlineFormsIntegrationTestCase
|
|
9
|
+
teardown do
|
|
10
|
+
[ Phase, Machine, Kind, Part ].each do |klass|
|
|
11
|
+
klass.remove_instance_variable(:@inline_forms_dropdown_order) if klass.instance_variable_defined?(:@inline_forms_dropdown_order)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def view
|
|
16
|
+
get machines_path
|
|
17
|
+
controller.view_context
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def option_texts(html)
|
|
21
|
+
html.scan(%r{<option(?: [^>]*)?>([^<]*)</option>}).flatten
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def create_phases
|
|
25
|
+
Phase.create!(name: "admittance", dropdown_order: 30)
|
|
26
|
+
Phase.create!(name: "nieuw")
|
|
27
|
+
Phase.create!(name: "decision", dropdown_order: 20)
|
|
28
|
+
Phase.create!(name: "---", dropdown_order: 0)
|
|
29
|
+
Phase.create!(name: "helprequest", dropdown_order: 10)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
test "a dropdown_order column orders the dropdown, NULLs last" do
|
|
33
|
+
create_phases
|
|
34
|
+
|
|
35
|
+
html = view.dropdown_edit(Part.create!(name: "Gear"), :phase)
|
|
36
|
+
|
|
37
|
+
assert_equal [ "---", "helprequest", "decision", "admittance", "nieuw" ], option_texts(html)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
test "the model's declared direction applies to the dropdown_order column" do
|
|
41
|
+
create_phases
|
|
42
|
+
Phase.inline_forms_dropdown_order :dropdown_order, :desc
|
|
43
|
+
|
|
44
|
+
html = view.dropdown_edit(Part.create!(name: "Gear"), :phase)
|
|
45
|
+
|
|
46
|
+
assert_equal [ "admittance", "decision", "helprequest", "---", "nieuw" ], option_texts(html)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
test "a declared column wins over the dropdown_order column" do
|
|
50
|
+
create_phases
|
|
51
|
+
Phase.inline_forms_dropdown_order :name, :desc
|
|
52
|
+
|
|
53
|
+
html = view.dropdown_edit(Part.create!(name: "Gear"), :phase)
|
|
54
|
+
|
|
55
|
+
assert_equal [ "nieuw", "helprequest", "decision", "admittance", "---" ], option_texts(html)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
test "without a declaration or column, the list order applies" do
|
|
59
|
+
%w[Zeta Alpha Mu].each { |name| Machine.create!(name: name) } # inline_forms_list: order(:name, :id)
|
|
60
|
+
|
|
61
|
+
html = view.dropdown_edit(Part.create!(name: "Gear"), :machine)
|
|
62
|
+
|
|
63
|
+
assert_equal %w[Alpha Mu Zeta], option_texts(html)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
test "ties on the declared column break on id" do
|
|
67
|
+
first = Phase.create!(name: "same", dropdown_order: 5)
|
|
68
|
+
second = Phase.create!(name: "same", dropdown_order: 5)
|
|
69
|
+
|
|
70
|
+
assert_equal [ first.id, second.id ], InlineForms::DropdownOrder.apply(Phase.all).map(&:id)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
test "without anything declared, records come in database order" do
|
|
74
|
+
%w[Zeta Alpha].each { |name| Kind.create!(name: name) } # inline_forms_list: all
|
|
75
|
+
|
|
76
|
+
assert_equal %w[Zeta Alpha], InlineForms::DropdownOrder.apply(Kind.all).map(&:name)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
test "dropdown_with_other offers its datalist in the dropdown order" do
|
|
80
|
+
%w[Zeta Alpha Mu].each { |name| Kind.create!(name: name) }
|
|
81
|
+
Kind.inline_forms_dropdown_order :name
|
|
82
|
+
|
|
83
|
+
html = view.dropdown_with_other_edit(Widget.create!(name: "W"), :kind)
|
|
84
|
+
|
|
85
|
+
assert_equal %w[Alpha Mu Zeta], html.scan(/<option value="([^"]*)">/).flatten
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
test "check_list lists its records in the dropdown order" do
|
|
89
|
+
machine = Machine.create!(name: "Press")
|
|
90
|
+
%w[Bolt Nut Axle].each { |name| Part.create!(name: name) }
|
|
91
|
+
Part.inline_forms_dropdown_order :name, :desc
|
|
92
|
+
|
|
93
|
+
html = view.check_list_edit(machine, :parts)
|
|
94
|
+
|
|
95
|
+
assert_equal %w[Nut Bolt Axle], html.scan(%r{<label for=[^>]*>([^<]*)</label>}).flatten.reject(&:empty?)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
test "an unknown direction or column is refused" do
|
|
99
|
+
assert_raises(ArgumentError) { Phase.inline_forms_dropdown_order :name, :up }
|
|
100
|
+
|
|
101
|
+
Phase.inline_forms_dropdown_order :position
|
|
102
|
+
assert_raises(ArgumentError) { InlineForms::DropdownOrder.apply(Phase.all).to_a }
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -17,7 +17,7 @@ class LocaleTest < InlineFormsIntegrationTestCase
|
|
|
17
17
|
inline_forms.view.versions_old_value inline_forms.view.versions_new_value inline_forms.view.versions_empty
|
|
18
18
|
inline_forms.view.versions_rich_text inline_forms.view.version_events.create
|
|
19
19
|
inline_forms.view.version_events.update inline_forms.view.version_events.destroy
|
|
20
|
-
inline_forms.devise.login common.more common.logout
|
|
20
|
+
inline_forms.devise.login common.more common.logout inline_forms.view.create_failed
|
|
21
21
|
].freeze
|
|
22
22
|
|
|
23
23
|
setup do
|
|
@@ -71,6 +71,24 @@ class LocaleTest < InlineFormsIntegrationTestCase
|
|
|
71
71
|
assert_equal "Machine is aangemaakt.", controller.flash[:success]
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
+
# The failure header was hard-coded Dutch with the underscored class name
|
|
75
|
+
# ("Kan machine niet aanmaken.") in every locale.
|
|
76
|
+
test "create's failure flash header translates, with the human model name" do
|
|
77
|
+
I18n.backend.store_translations(:nl, machines: "machines",
|
|
78
|
+
activerecord: { models: { machine: "Werkbank" } })
|
|
79
|
+
strictly(:nl) do
|
|
80
|
+
post machines_path(update: "machines_list"), params: { name: "" }, headers: frame_headers("machines_list")
|
|
81
|
+
end
|
|
82
|
+
assert_equal [ "Kan Werkbank niet aanmaken." ], controller.flash[:header]
|
|
83
|
+
|
|
84
|
+
I18n.backend.store_translations(:en, machines: "machines")
|
|
85
|
+
strictly(:en) do
|
|
86
|
+
post machines_path(update: "machines_list"), params: { name: "" }, headers: frame_headers("machines_list")
|
|
87
|
+
end
|
|
88
|
+
assert_equal [ "Could not create Machine." ], controller.flash[:header]
|
|
89
|
+
assert_includes response.body, "Could not create Machine."
|
|
90
|
+
end
|
|
91
|
+
|
|
74
92
|
test "the versions panel and list are translated, with localized timestamps" do
|
|
75
93
|
row = "machine_#{@machine.id}"
|
|
76
94
|
strictly(:nl) do
|
|
@@ -42,9 +42,18 @@ ActiveRecord::Schema.define do
|
|
|
42
42
|
t.timestamps
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
+
# Lookup with a dropdown_order column (InlineForms::DropdownOrder);
|
|
46
|
+
# Part belongs_to it for the dropdown tests.
|
|
47
|
+
create_table :phases, force: true do |t|
|
|
48
|
+
t.string :name
|
|
49
|
+
t.integer :dropdown_order
|
|
50
|
+
t.timestamps
|
|
51
|
+
end
|
|
52
|
+
|
|
45
53
|
create_table :parts, force: true do |t|
|
|
46
54
|
t.string :name
|
|
47
55
|
t.integer :machine_id
|
|
56
|
+
t.integer :phase_id
|
|
48
57
|
t.timestamps
|
|
49
58
|
end
|
|
50
59
|
add_index :parts, :machine_id
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: inline_forms
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 8.1.
|
|
4
|
+
version: 8.1.59
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ace Suares
|
|
@@ -223,6 +223,7 @@ files:
|
|
|
223
223
|
- lib/inline_forms.rb
|
|
224
224
|
- lib/inline_forms/archived_form_elements.rb
|
|
225
225
|
- lib/inline_forms/attribute_list.rb
|
|
226
|
+
- lib/inline_forms/dropdown_order.rb
|
|
226
227
|
- lib/inline_forms/form_element_from_callee.rb
|
|
227
228
|
- lib/inline_forms/form_element_registry.rb
|
|
228
229
|
- lib/inline_forms/form_elements.rb
|
|
@@ -299,6 +300,7 @@ files:
|
|
|
299
300
|
- test/dummy/app/models/kind.rb
|
|
300
301
|
- test/dummy/app/models/machine.rb
|
|
301
302
|
- test/dummy/app/models/part.rb
|
|
303
|
+
- test/dummy/app/models/phase.rb
|
|
302
304
|
- test/dummy/app/models/widget.rb
|
|
303
305
|
- test/dummy/app/views/_gizmos_search.html.erb
|
|
304
306
|
- test/dummy/app/views/stats/show.html.erb
|
|
@@ -314,6 +316,7 @@ files:
|
|
|
314
316
|
- test/inline_forms_addto_generator_test.rb
|
|
315
317
|
- test/inline_forms_generator_test.rb
|
|
316
318
|
- test/integration/csp_nonce_test.rb
|
|
319
|
+
- test/integration/dropdown_test.rb
|
|
317
320
|
- test/integration/field_authorization_test.rb
|
|
318
321
|
- test/integration/file_slots_test.rb
|
|
319
322
|
- test/integration/html_refusal_test.rb
|
|
@@ -387,6 +390,7 @@ test_files:
|
|
|
387
390
|
- test/dummy/app/models/kind.rb
|
|
388
391
|
- test/dummy/app/models/machine.rb
|
|
389
392
|
- test/dummy/app/models/part.rb
|
|
393
|
+
- test/dummy/app/models/phase.rb
|
|
390
394
|
- test/dummy/app/models/widget.rb
|
|
391
395
|
- test/dummy/app/views/_gizmos_search.html.erb
|
|
392
396
|
- test/dummy/app/views/stats/show.html.erb
|
|
@@ -402,6 +406,7 @@ test_files:
|
|
|
402
406
|
- test/inline_forms_addto_generator_test.rb
|
|
403
407
|
- test/inline_forms_generator_test.rb
|
|
404
408
|
- test/integration/csp_nonce_test.rb
|
|
409
|
+
- test/integration/dropdown_test.rb
|
|
405
410
|
- test/integration/field_authorization_test.rb
|
|
406
411
|
- test/integration/file_slots_test.rb
|
|
407
412
|
- test/integration/html_refusal_test.rb
|