formatic 0.4.4 → 0.6.0

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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/README.md +38 -0
  4. data/app/assets/javascript/declarations/search.d.ts +39 -0
  5. data/app/assets/javascript/declarations/search_record.d.ts +9 -0
  6. data/app/assets/javascript/declarations/search_requests.d.ts +13 -0
  7. data/app/assets/javascript/formatic/search.js +188 -0
  8. data/app/assets/javascript/formatic/search.js.map +1 -0
  9. data/app/assets/javascript/formatic/search_record.js +21 -0
  10. data/app/assets/javascript/formatic/search_record.js.map +1 -0
  11. data/app/assets/javascript/formatic/search_requests.js +72 -0
  12. data/app/assets/javascript/formatic/search_requests.js.map +1 -0
  13. data/app/assets/javascript/formatic/setup.js +6 -0
  14. data/app/assets/javascript/src/search.ts +242 -0
  15. data/app/assets/javascript/src/search_record.ts +24 -0
  16. data/app/assets/javascript/src/search_requests.ts +94 -0
  17. data/app/assets/stylesheets/formatic/components/search.sass +51 -0
  18. data/app/assets/stylesheets/formatic/formatic.css +25 -0
  19. data/app/assets/stylesheets/formatic/formatic.css.map +1 -1
  20. data/app/assets/stylesheets/formatic.sass +1 -0
  21. data/app/components/formatic/file.rb +27 -20
  22. data/app/components/formatic/search/choice.rb +14 -0
  23. data/app/components/formatic/search/clear.rb +12 -0
  24. data/app/components/formatic/search/frontend.rb +21 -0
  25. data/app/components/formatic/search/selected.rb +19 -0
  26. data/app/components/formatic/search.rb +30 -0
  27. data/config/importmap.rb +3 -0
  28. data/lib/formatic/engine.rb +5 -0
  29. data/lib/formatic/templates/search.rb +51 -0
  30. data/lib/formatic/testing/capybara.rb +20 -0
  31. data/lib/formatic/testing/search.rb +87 -0
  32. data/lib/formatic/version.rb +1 -1
  33. metadata +22 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 032f7e8e0a8d71e094692ff1fee875c5514874ddf79876bba8d7026bcc3769df
4
- data.tar.gz: dd414a5305acca154f715389fe88e3e0950d8146964396d7a524424a36edadd8
3
+ metadata.gz: d41f61aacdbc9679d1e63ff82441ac6008ae35f53a8b5425986e3bac53baeb0d
4
+ data.tar.gz: 2974cac3f7d742dc001b45a2c5cdfe81f71e4e2cfd24d6052b4d563a790423c7
5
5
  SHA512:
6
- metadata.gz: 60f8b3103a1a0948a4226c4311a5a44d7f65bc5ce48c205859b2fba5e4a73cf8bf46860382ab3b8f8ec546fe42612c0feac34d3b47863215928c1b3f76ef5512
7
- data.tar.gz: 3538770fe0cccc3e88d069340e2b0cb2914951f46fe45bd7bd64ebf7e72b366d5c6373e209ee2bdb2749159d3bcd150035066b18fa3eb5f5c66832b892501dfa
6
+ metadata.gz: 4da00730403d13520acf91dd762fe73e9a007ba6ba4146aa96cfb40d9e18ee71e4d45eb54569fab08a87df55838ee6c6302b1881c6b4a8eaa3376b10cf91462f
7
+ data.tar.gz: 7d37f9de2bb33f902fb85390aa8bcdc0f1500131196cf55107eeaaa5df94cd78e22bb34bc88cadaae41ac20457eb3d74d34053989da08d457d7e2a04f99cfd51
data/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## [0.6.0] 2026-10-26
2
+
3
+ - Include `Formatic::Testing::Capybara::Search` in a Capybara RackTest class to call `search 'Acme Corp', from: 'Customer'`
4
+
5
+ ## [0.5.1] 2026-10-26
6
+
7
+ - Rack tests call `Formatic::Testing::Search.call` instead of including a Capybara module
8
+
9
+ ## [0.5.0] 2026-10-26
10
+
11
+ - Add `Formatic::Search`, a JSON-backed search field
12
+ - `Formatic::Search::Frontend.json` wraps the choice and selected partials
13
+ - The selected card includes the clear control. A readonly field omits the query and the clear control
14
+
1
15
  ## [0.4.4] 2026-09-18
2
16
 
3
17
  - Restrict year select to current year and later when `skip_past` is true
data/README.md CHANGED
@@ -9,6 +9,44 @@ Opinionated ViewComponents for building forms.
9
9
  = render Formatic::String.new(f:, attribute_name: :first_name)
10
10
  ```
11
11
 
12
+ ### Search fields
13
+
14
+ `Formatic::Search` renders a hidden value input and a query input for an endpoint
15
+ that returns JSON in the form `{ records: [...] }`. Each record has an `id`, a
16
+ `label`, `html_for_autocomplete`, and `html_for_input` properties. The
17
+ `html_for_autocomplete` markup must include one `.js-formatic-search__choice`
18
+ element per record, in the same order as `records`. `html_for_input` provides
19
+ the selected-value markup. Search waits 400 ms after the last input. A newer
20
+ query aborts the previous request for that field, and a response is shown only
21
+ when its query still matches the input. An optional `with_clear` slot clears
22
+ the current selection.
23
+ An optional `with_add_record` slot stays hidden while searching and appears
24
+ after a successful response, even when there are no matches.
25
+
26
+ ```ruby
27
+ = render Formatic::Search.new(f:, attribute_name: :commander_id,
28
+ record_name: 'commander',
29
+ url: admin_commanders_path(format: :json))
30
+ ```
31
+
32
+ For Rack tests, call `Formatic::Testing::Search.call(page:, query: 'Bob', from: 'Users')`.
33
+ It reads the field's URL and parameter names, requests matching JSON directly, and
34
+ fills the hidden input with a result returned by that endpoint.
35
+
36
+ Capybara RackTest test classes can include the `search` helper instead:
37
+
38
+ ```ruby
39
+ require 'formatic/testing/capybara'
40
+
41
+ class CustomersTest < ActionDispatch::SystemTestCase
42
+ include Formatic::Testing::Capybara::Search
43
+
44
+ test 'select a customer' do
45
+ search 'Acme Corp', from: 'Customer'
46
+ end
47
+ end
48
+ ```
49
+
12
50
  ## Installation
13
51
 
14
52
  Install the gem and add to the application's Gemfile by executing:
@@ -0,0 +1,39 @@
1
+ export declare class Search {
2
+ private el;
3
+ private requests;
4
+ private choiceRecords;
5
+ private timeoutId;
6
+ constructor(el: HTMLElement);
7
+ private setupBindings;
8
+ private clicked;
9
+ private debounce;
10
+ private actionSearch;
11
+ private actionSearchNow;
12
+ private actionChoose;
13
+ private actionClear;
14
+ private guiWaitingForQuery;
15
+ private guiSearchingRecords;
16
+ private guiFailed;
17
+ private guiPopulateRecordChoices;
18
+ private guiChooseRecord;
19
+ private guiClear;
20
+ private guiMayAddNew;
21
+ private guiMayNotAddNew;
22
+ private notifyValueChanged;
23
+ private choicesFor;
24
+ private get searchParams();
25
+ private get resourceUrl();
26
+ private get recordName();
27
+ private get attributeName();
28
+ private get excludeIds();
29
+ private get resourceInput();
30
+ private get recordNameInput();
31
+ private get attributeNameInput();
32
+ private get excludeIdsInput();
33
+ private get queryInput();
34
+ private get valueInput();
35
+ private get selectedContainer();
36
+ private get resultsContainer();
37
+ private get choices();
38
+ private get addRecordContainer();
39
+ }
@@ -0,0 +1,9 @@
1
+ export declare class SearchRecord {
2
+ id: string | number;
3
+ choice: string;
4
+ selected: string;
5
+ constructor(attributes: Partial<SearchRecord>);
6
+ get recordId(): string;
7
+ get renderedChoice(): string;
8
+ get renderedSelected(): string;
9
+ }
@@ -0,0 +1,13 @@
1
+ export declare class SearchRequests {
2
+ private previousController;
3
+ private currentController;
4
+ search(resource: string, searchterm: string, params: {
5
+ [key: string]: string;
6
+ }): Promise<any>;
7
+ abort(): void;
8
+ private perform;
9
+ private abortPreviousRequests;
10
+ private parseResponse;
11
+ private fail;
12
+ private get requestOptions();
13
+ }
@@ -0,0 +1,188 @@
1
+ import { SearchRecord } from 'formatic/search_record';
2
+ import { SearchRequests } from 'formatic/search_requests';
3
+ export class Search {
4
+ constructor(el) {
5
+ this.el = el;
6
+ this.requests = new SearchRequests;
7
+ this.choiceRecords = new Map();
8
+ this.setupBindings();
9
+ }
10
+ setupBindings() {
11
+ if (!this.queryInput)
12
+ return;
13
+ this.resultsContainer.addEventListener('click', (event) => {
14
+ const choice = this.clicked('.js-formatic-search__choice', event);
15
+ if (!choice)
16
+ return;
17
+ event.preventDefault();
18
+ this.actionChoose(choice);
19
+ });
20
+ this.el.addEventListener('click', (event) => {
21
+ const clear = this.clicked('.js-formatic-search__clear', event);
22
+ if (!clear)
23
+ return;
24
+ event.preventDefault();
25
+ this.actionClear();
26
+ });
27
+ this.queryInput.addEventListener('input', (event) => {
28
+ event.preventDefault();
29
+ this.actionSearch();
30
+ });
31
+ }
32
+ clicked(selector, event) {
33
+ const target = event.target;
34
+ const receiver = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
35
+ const match = receiver?.closest(selector);
36
+ if (!match || !this.el.contains(match))
37
+ return null;
38
+ return match;
39
+ }
40
+ debounce(fn) {
41
+ return () => {
42
+ clearTimeout(this.timeoutId);
43
+ this.timeoutId = setTimeout(() => fn(), 400);
44
+ };
45
+ }
46
+ actionSearch() {
47
+ this.guiWaitingForQuery();
48
+ this.debounce(this.actionSearchNow.bind(this))();
49
+ }
50
+ actionSearchNow() {
51
+ if (this.queryInput.value.length == 0) {
52
+ this.resultsContainer.innerHTML = '';
53
+ return;
54
+ }
55
+ this.guiSearchingRecords();
56
+ this.requests.search(this.resourceUrl, this.queryInput.value, this.searchParams)
57
+ .then(result => {
58
+ if (result?.query != this.queryInput.value)
59
+ return;
60
+ return result.data.records.map((attributes) => new SearchRecord(attributes));
61
+ }).then(records => {
62
+ if (records)
63
+ this.guiPopulateRecordChoices(records);
64
+ }).catch(err => {
65
+ console.warn(`Finding records failed: ${err}`);
66
+ this.guiFailed();
67
+ });
68
+ }
69
+ actionChoose(choice) {
70
+ clearTimeout(this.timeoutId);
71
+ this.requests.abort();
72
+ const record = this.choiceRecords.get(choice);
73
+ if (!record)
74
+ throw new Error('No search result for the chosen element');
75
+ this.guiChooseRecord(record);
76
+ }
77
+ actionClear() {
78
+ clearTimeout(this.timeoutId);
79
+ this.requests.abort();
80
+ this.guiClear();
81
+ }
82
+ guiWaitingForQuery() {
83
+ this.resultsContainer.textContent = 'Waiting for input...';
84
+ this.guiMayNotAddNew();
85
+ }
86
+ guiSearchingRecords() {
87
+ this.resultsContainer.textContent = 'Searching...';
88
+ this.guiMayNotAddNew();
89
+ }
90
+ guiFailed() {
91
+ this.choiceRecords.clear();
92
+ this.resultsContainer.textContent = 'Search failed.';
93
+ this.guiMayNotAddNew();
94
+ }
95
+ guiPopulateRecordChoices(records) {
96
+ this.resultsContainer.innerHTML = records.map((record) => record.renderedChoice).join('');
97
+ this.choiceRecords = this.choicesFor(records);
98
+ if (records.length == 0)
99
+ this.resultsContainer.textContent = 'No records found.';
100
+ this.guiMayAddNew();
101
+ }
102
+ guiChooseRecord(record) {
103
+ this.valueInput.value = record.recordId;
104
+ this.selectedContainer.innerHTML = record.renderedSelected;
105
+ this.resultsContainer.innerHTML = '';
106
+ this.queryInput.value = '';
107
+ this.choiceRecords.clear();
108
+ this.guiMayNotAddNew();
109
+ this.notifyValueChanged();
110
+ }
111
+ guiClear() {
112
+ this.valueInput.value = '';
113
+ this.selectedContainer.innerHTML = '';
114
+ this.resultsContainer.innerHTML = '';
115
+ this.queryInput.value = '';
116
+ this.choiceRecords.clear();
117
+ this.guiMayNotAddNew();
118
+ this.notifyValueChanged();
119
+ }
120
+ guiMayAddNew() {
121
+ this.addRecordContainer.hidden = false;
122
+ }
123
+ guiMayNotAddNew() {
124
+ this.addRecordContainer.hidden = true;
125
+ }
126
+ notifyValueChanged() {
127
+ this.valueInput.dispatchEvent(new Event('change', { bubbles: true }));
128
+ }
129
+ choicesFor(records) {
130
+ const choices = Array.from(this.choices);
131
+ if (choices.length != records.length) {
132
+ throw new Error(`Expected ${records.length} .js-formatic-search__choice elements, found ${choices.length}`);
133
+ }
134
+ return new Map(choices.map((choice, index) => [choice, records[index]]));
135
+ }
136
+ get searchParams() {
137
+ const params = {
138
+ record_name: this.recordName,
139
+ attribute_name: this.attributeName,
140
+ };
141
+ if (this.excludeIds)
142
+ params.exclude_ids = this.excludeIds;
143
+ return params;
144
+ }
145
+ get resourceUrl() {
146
+ return this.resourceInput.value;
147
+ }
148
+ get recordName() {
149
+ return this.recordNameInput.value;
150
+ }
151
+ get attributeName() {
152
+ return this.attributeNameInput.value;
153
+ }
154
+ get excludeIds() {
155
+ return this.excludeIdsInput.value;
156
+ }
157
+ get resourceInput() {
158
+ return this.el.querySelector('.js-formatic-search__url');
159
+ }
160
+ get recordNameInput() {
161
+ return this.el.querySelector('.js-formatic-search__record-name');
162
+ }
163
+ get attributeNameInput() {
164
+ return this.el.querySelector('.js-formatic-search__attribute-name');
165
+ }
166
+ get excludeIdsInput() {
167
+ return this.el.querySelector('.js-formatic-search__exclude-ids');
168
+ }
169
+ get queryInput() {
170
+ return this.el.querySelector('.js-formatic-search__query-input');
171
+ }
172
+ get valueInput() {
173
+ return this.el.querySelector('.js-formatic-search__value');
174
+ }
175
+ get selectedContainer() {
176
+ return this.el.querySelector('.js-formatic-search__selected');
177
+ }
178
+ get resultsContainer() {
179
+ return this.el.querySelector('.js-formatic-search__results');
180
+ }
181
+ get choices() {
182
+ return this.resultsContainer.querySelectorAll('.js-formatic-search__choice');
183
+ }
184
+ get addRecordContainer() {
185
+ return this.el.querySelector('.js-formatic-search__add-record');
186
+ }
187
+ }
188
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../src/search.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ export class SearchRecord {
2
+ constructor(attributes) {
3
+ Object.assign(this, attributes);
4
+ }
5
+ get recordId() {
6
+ if (this.id == null || this.id == '')
7
+ throw new Error('Search record is missing id');
8
+ return String(this.id);
9
+ }
10
+ get renderedChoice() {
11
+ if (!this.choice)
12
+ throw new Error('Search record is missing choice');
13
+ return this.choice;
14
+ }
15
+ get renderedSelected() {
16
+ if (!this.selected)
17
+ throw new Error('Search record is missing selected');
18
+ return this.selected;
19
+ }
20
+ }
21
+ //# sourceMappingURL=search_record.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search_record.js","sourceRoot":"","sources":["../src/search_record.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,YAAY;IAKvB,YAAY,UAAiC;QAC3C,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,CAAA;IACjC,CAAC;IAED,IAAW,QAAQ;QACjB,IAAI,IAAI,CAAC,EAAE,IAAI,IAAI,IAAI,IAAI,CAAC,EAAE,IAAI,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAA;QACpF,OAAO,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACxB,CAAC;IAED,IAAW,cAAc;QACvB,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAA;QACpE,OAAO,IAAI,CAAC,MAAM,CAAA;IACpB,CAAC;IAED,IAAW,gBAAgB;QACzB,IAAI,CAAC,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAA;QACxE,OAAO,IAAI,CAAC,QAAQ,CAAA;IACtB,CAAC;CACF"}
@@ -0,0 +1,72 @@
1
+ export class SearchRequests {
2
+ search(resource, searchterm, params) {
3
+ const options = new Options;
4
+ options.resource = resource;
5
+ options.params = { ...params };
6
+ options.params.query = searchterm;
7
+ return this.perform(options);
8
+ }
9
+ abort() {
10
+ if (this.previousController)
11
+ this.previousController.abort();
12
+ }
13
+ perform(options) {
14
+ this.abortPreviousRequests();
15
+ return new Promise((resolve, reject) => {
16
+ if (options.params.query == '')
17
+ return resolve(null);
18
+ console.debug(`SearchRequests GET ${options.url}`);
19
+ fetch(options.url, this.requestOptions)
20
+ .then(response => this.parseResponse(response))
21
+ .then(data => {
22
+ resolve(new Result({ data: data, options: options, query: options.params.query }));
23
+ }).catch(err => {
24
+ this.fail(err, options, reject);
25
+ });
26
+ });
27
+ }
28
+ abortPreviousRequests() {
29
+ this.abort();
30
+ this.currentController = new AbortController();
31
+ this.previousController = this.currentController;
32
+ }
33
+ parseResponse(response) {
34
+ if (!response.ok)
35
+ throw Error(response.statusText);
36
+ return response.json();
37
+ }
38
+ fail(err, options, reject) {
39
+ if (err.name == 'AbortError') {
40
+ console.debug(`Cancelled GET ${options.url}`);
41
+ return;
42
+ }
43
+ console.debug(`Failed to GET ${options.url}`);
44
+ reject(err);
45
+ }
46
+ get requestOptions() {
47
+ return {
48
+ method: 'GET',
49
+ headers: {
50
+ 'Accept': 'application/json',
51
+ 'Content-Type': 'application/json',
52
+ },
53
+ signal: this.currentController.signal,
54
+ };
55
+ }
56
+ }
57
+ class Options {
58
+ get url() {
59
+ const requestParams = new URLSearchParams();
60
+ for (const [key, value] of Object.entries(this.params)) {
61
+ requestParams.set(key, value);
62
+ }
63
+ const separator = this.resource.includes('?') ? '&' : '?';
64
+ return this.resource + separator + requestParams;
65
+ }
66
+ }
67
+ class Result {
68
+ constructor(attributes) {
69
+ Object.assign(this, attributes);
70
+ }
71
+ }
72
+ //# sourceMappingURL=search_requests.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search_requests.js","sourceRoot":"","sources":["../src/search_requests.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import { Date } from 'formatic/date'
2
2
  import { Select } from 'formatic/select'
3
+ import { Search } from 'formatic/search'
3
4
  import { Stepper } from 'formatic/stepper'
4
5
  import { String } from 'formatic/string'
5
6
  import { Textarea } from 'formatic/textarea'
@@ -7,6 +8,11 @@ import { Toggle } from 'formatic/toggle'
7
8
  import { FormaticFile } from 'formatic/file'
8
9
 
9
10
  document.addEventListener('DOMContentLoaded', () => {
11
+ document.querySelectorAll('.js-formatic-search').forEach((el) => {
12
+ console.debug('[Formatic] Instantiating Search...')
13
+ new Search(el)
14
+ })
15
+
10
16
  document.querySelectorAll('.js-formatic-file').forEach((el) => {
11
17
  console.debug('[Formatic] Instantiating File...')
12
18
  new FormaticFile(el)