tam_select 0.1.1 → 1.1.1
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/README.md +123 -6
- data/lib/generators/tam_select/install_generator.rb +1 -0
- data/lib/generators/tam_select/templates/tam_select_remote.rb +54 -0
- data/lib/generators/tam_select/templates/tam_select_remote_controller.rb +3 -41
- data/lib/tam_select/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3cdee3c229caaeda5d7f572395009c1ffd03e58c1f5376c215ced0e751c35fb9
|
|
4
|
+
data.tar.gz: 9896dd22aa1dc9af3727596c1bee1d82ca6b8f1462a3e24a5177b10306853888
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 271d3b88383037c504189080665c0c15e19a640e6f8efe0d580683d9f543a0a56ff496a667064b788fbd8a8f1ddc8f48512d755cfc10985ec043d36c042891ce
|
|
7
|
+
data.tar.gz: be82081ad386749cb1ff40f1cd48ca80417104712c417de25dccc969082f3f2edfce09a912bce53467c34e170b1a0235245caede8d53717b7fff5d3b21d8f072
|
data/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
# Tam Select
|
|
1
|
+
the # Tam Select
|
|
2
2
|
|
|
3
3
|
[](LICENSE)
|
|
4
|
+
[](https://github.com/tamiru/tam_select)
|
|
4
5
|
|
|
5
6
|
**Tam Select** is an accessible, searchable select component for Ruby on Rails, built for Simple Form, Stimulus, Turbo, and Tailwind CSS. It keeps the native `<select>` as the source of truth, so Rails form submission, validation, selected values, and browser autofill continue to work.
|
|
6
7
|
|
|
@@ -35,6 +36,7 @@ app/javascript/tam_select/tam_select.js
|
|
|
35
36
|
app/javascript/controllers/tam_select_controller.js
|
|
36
37
|
app/inputs/tam_select_input.rb
|
|
37
38
|
app/controllers/concerns/tam_select_paginatable.rb
|
|
39
|
+
app/controllers/concerns/tam_select_remote.rb
|
|
38
40
|
app/controllers/tam_select_remote_controller.rb
|
|
39
41
|
app/helpers/tam_select_helper.rb
|
|
40
42
|
```
|
|
@@ -48,6 +50,23 @@ Add the generated JavaScript to Tailwind's source detection in `app/assets/tailw
|
|
|
48
50
|
@source "../../javascript/tam_select/**/*.js";
|
|
49
51
|
```
|
|
50
52
|
|
|
53
|
+
### Updating
|
|
54
|
+
|
|
55
|
+
The Rails integration files are copied into your application, so updating the gem does not update them automatically. Commit local customizations first, then run:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
bundle update tam_select
|
|
59
|
+
bin/rails generate tam_select:install
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Rails prompts before replacing changed files. To replace every generated file without prompting, use:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
bin/rails generate tam_select:install --force
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Review `git diff` afterward because `--force` overwrites application-specific customizations.
|
|
69
|
+
|
|
51
70
|
## Features
|
|
52
71
|
|
|
53
72
|
- Single and multiple selection
|
|
@@ -113,7 +132,9 @@ Stimulus destroys generated markup when Turbo removes the select and recreates i
|
|
|
113
132
|
|
|
114
133
|
## Simple Form
|
|
115
134
|
|
|
116
|
-
The install generator creates `app/inputs/tam_select_input.rb`. Use
|
|
135
|
+
The install generator creates `app/inputs/tam_select_input.rb`. Use `as: :tam_select` with any Simple Form collection input.
|
|
136
|
+
|
|
137
|
+
### Local collection
|
|
117
138
|
|
|
118
139
|
```erb
|
|
119
140
|
<%= form.input :region_id,
|
|
@@ -121,16 +142,49 @@ The install generator creates `app/inputs/tam_select_input.rb`. Use it like any
|
|
|
121
142
|
collection: Region.order(:name),
|
|
122
143
|
label_method: :name,
|
|
123
144
|
value_method: :id,
|
|
145
|
+
prompt: "Select region" %>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Remote collection
|
|
149
|
+
|
|
150
|
+
Keep the currently selected record in the initial collection so edit forms can display its label before the AJAX request completes:
|
|
151
|
+
|
|
152
|
+
```erb
|
|
153
|
+
<%= form.input :region_id,
|
|
154
|
+
as: :tam_select,
|
|
155
|
+
collection: [form.object.region].compact,
|
|
156
|
+
label_method: :name,
|
|
157
|
+
value_method: :id,
|
|
124
158
|
prompt: "Select region",
|
|
125
159
|
input_html: {
|
|
126
160
|
tam_options: {
|
|
127
|
-
remoteUrl:
|
|
128
|
-
minQueryLength: 1
|
|
161
|
+
remoteUrl: tam_select_options_regions_path(format: :json),
|
|
162
|
+
minQueryLength: 1,
|
|
163
|
+
debounce: 250
|
|
164
|
+
}
|
|
165
|
+
} %>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The concern and route required by this example are described in [Add remote search to an existing controller](#add-remote-search-to-an-existing-controller).
|
|
169
|
+
|
|
170
|
+
### Multiple selection
|
|
171
|
+
|
|
172
|
+
```erb
|
|
173
|
+
<%= form.input :skill_ids,
|
|
174
|
+
as: :tam_select,
|
|
175
|
+
collection: Skill.order(:name),
|
|
176
|
+
label_method: :name,
|
|
177
|
+
value_method: :id,
|
|
178
|
+
input_html: {
|
|
179
|
+
multiple: true,
|
|
180
|
+
tam_options: {
|
|
181
|
+
searchable: true,
|
|
182
|
+
closeAfterSelect: false
|
|
129
183
|
}
|
|
130
184
|
} %>
|
|
131
185
|
```
|
|
132
186
|
|
|
133
|
-
|
|
187
|
+
Options are passed under `input_html[:tam_options]`. Common options include `searchable`, `creatable`, `clearable`, `placeholder`, `searchPlaceholder`, `remoteUrl`, `minQueryLength`, and `debounce`. See [Main options](#main-options) for defaults.
|
|
134
188
|
|
|
135
189
|
## Remote API contract
|
|
136
190
|
|
|
@@ -154,6 +208,53 @@ The generator installs `TamSelectPaginatable` as a Rails response helper. A comp
|
|
|
154
208
|
|
|
155
209
|
Secure remote endpoints exactly like other Rails JSON endpoints. Scope records by the current user's permissions and never trust a submitted value merely because it appeared in the dropdown.
|
|
156
210
|
|
|
211
|
+
### Add remote search to an existing controller
|
|
212
|
+
|
|
213
|
+
Include `TamSelectRemote` and declare the allowed model and searchable fields. This adds the public `tam_select_options` action to the controller:
|
|
214
|
+
|
|
215
|
+
```ruby
|
|
216
|
+
# app/controllers/regions_controller.rb
|
|
217
|
+
class RegionsController < ApplicationController
|
|
218
|
+
include TamSelectRemote
|
|
219
|
+
|
|
220
|
+
tam_select_remote(
|
|
221
|
+
model: Region,
|
|
222
|
+
label: :name,
|
|
223
|
+
value: :id,
|
|
224
|
+
search_by: %i[name code],
|
|
225
|
+
scope: -> { Region.order(:name) },
|
|
226
|
+
per_page: 20
|
|
227
|
+
)
|
|
228
|
+
end
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
The optional `scope` lambda runs in the controller context, so it can use `current_user`, `current_account`, or an authorization policy.
|
|
232
|
+
|
|
233
|
+
```ruby
|
|
234
|
+
# config/routes.rb
|
|
235
|
+
resources :regions do
|
|
236
|
+
get :tam_select_options, on: :collection, defaults: { format: :json }
|
|
237
|
+
end
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Point Simple Form to that collection action:
|
|
241
|
+
|
|
242
|
+
```erb
|
|
243
|
+
<%= form.input :region_id,
|
|
244
|
+
as: :tam_select,
|
|
245
|
+
collection: [form.object.region].compact,
|
|
246
|
+
label_method: :name,
|
|
247
|
+
value_method: :id,
|
|
248
|
+
input_html: {
|
|
249
|
+
tam_options: {
|
|
250
|
+
remoteUrl: tam_select_options_regions_path(format: :json),
|
|
251
|
+
minQueryLength: 1
|
|
252
|
+
}
|
|
253
|
+
} %>
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Typing sends `GET /regions/tam_select_options.json?q=addis&page=1` and receives the standard Tam Select JSON payload.
|
|
257
|
+
|
|
157
258
|
### Generic remote controller
|
|
158
259
|
|
|
159
260
|
The installer generates `TamSelectRemoteController`. Create a small subclass for each allowed remote source:
|
|
@@ -187,7 +288,7 @@ get "region_options", to: "region_options#index", defaults: { format: :json }
|
|
|
187
288
|
value_method: :id,
|
|
188
289
|
input_html: {
|
|
189
290
|
tam_options: {
|
|
190
|
-
remoteUrl: region_options_path,
|
|
291
|
+
remoteUrl: region_options_path(format: :json),
|
|
191
292
|
minQueryLength: 1
|
|
192
293
|
}
|
|
193
294
|
} %>
|
|
@@ -195,6 +296,22 @@ get "region_options", to: "region_options#index", defaults: { format: :json }
|
|
|
195
296
|
|
|
196
297
|
Do not accept a model name from request parameters. Declaring each source in a controller subclass prevents clients from querying arbitrary application models.
|
|
197
298
|
|
|
299
|
+
The browser sends requests such as:
|
|
300
|
+
|
|
301
|
+
```text
|
|
302
|
+
GET /region_options.json?q=addis&page=1
|
|
303
|
+
Accept: application/json
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Test an endpoint independently with:
|
|
307
|
+
|
|
308
|
+
```bash
|
|
309
|
+
curl -H "Accept: application/json" \
|
|
310
|
+
"http://localhost:3000/region_options.json?q=addis&page=1"
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
A `406 Not Acceptable` response means the route or controller rejected JSON. Keep `defaults: { format: :json }` on the route, use a `.json` URL, and ensure the controller does not restrict responses to HTML only.
|
|
314
|
+
|
|
198
315
|
## Core JavaScript
|
|
199
316
|
|
|
200
317
|
```js
|
|
@@ -18,6 +18,7 @@ module TamSelect
|
|
|
18
18
|
|
|
19
19
|
def copy_controller_concern
|
|
20
20
|
copy_file "lib/generators/tam_select/templates/tam_select_paginatable.rb", "app/controllers/concerns/tam_select_paginatable.rb"
|
|
21
|
+
copy_file "lib/generators/tam_select/templates/tam_select_remote.rb", "app/controllers/concerns/tam_select_remote.rb"
|
|
21
22
|
end
|
|
22
23
|
|
|
23
24
|
def copy_remote_controller
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module TamSelectRemote
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
included do
|
|
5
|
+
include TamSelectPaginatable
|
|
6
|
+
class_attribute :tam_select_remote_config, instance_writer: false
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
class_methods do
|
|
10
|
+
def tam_select_remote(model:, label:, value: :id, search_by: nil, scope: nil, per_page: 20)
|
|
11
|
+
self.tam_select_remote_config = {
|
|
12
|
+
model: model,
|
|
13
|
+
label: label,
|
|
14
|
+
value: value,
|
|
15
|
+
search_by: Array(search_by || label),
|
|
16
|
+
scope: scope,
|
|
17
|
+
per_page: per_page
|
|
18
|
+
}.freeze
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def tam_select_options
|
|
23
|
+
config = tam_select_remote_config!
|
|
24
|
+
records = tam_select_remote_search(tam_select_remote_scope(config), config)
|
|
25
|
+
|
|
26
|
+
render json: tam_select_payload(
|
|
27
|
+
records,
|
|
28
|
+
label: config[:label],
|
|
29
|
+
value: config[:value],
|
|
30
|
+
per_page: config[:per_page]
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def tam_select_remote_config!
|
|
37
|
+
self.class.tam_select_remote_config ||
|
|
38
|
+
raise("Configure #{self.class.name} with tam_select_remote(...)")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def tam_select_remote_scope(config)
|
|
42
|
+
config[:scope] ? instance_exec(&config[:scope]) : config[:model].all
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def tam_select_remote_search(records, config)
|
|
46
|
+
query = params[:q].to_s.strip
|
|
47
|
+
return records if query.blank?
|
|
48
|
+
|
|
49
|
+
model = config[:model]
|
|
50
|
+
pattern = "%#{model.sanitize_sql_like(query)}%"
|
|
51
|
+
predicates = config[:search_by].map { |field| model.arel_table[field].matches(pattern) }
|
|
52
|
+
records.where(predicates.reduce(&:or))
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -1,44 +1,6 @@
|
|
|
1
1
|
class TamSelectRemoteController < ApplicationController
|
|
2
|
-
include
|
|
2
|
+
include TamSelectRemote
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def self.tam_select(model:, label:, value: :id, search_by: nil, per_page: 20)
|
|
7
|
-
self.tam_select_config = {
|
|
8
|
-
model: model,
|
|
9
|
-
label: label,
|
|
10
|
-
value: value,
|
|
11
|
-
search_by: Array(search_by || label),
|
|
12
|
-
per_page: per_page
|
|
13
|
-
}.freeze
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def index
|
|
17
|
-
config = self.class.tam_select_config
|
|
18
|
-
raise "Configure #{self.class.name} with tam_select(...)" unless config
|
|
19
|
-
|
|
20
|
-
records = search(tam_select_scope(config), config)
|
|
21
|
-
render json: tam_select_payload(
|
|
22
|
-
records,
|
|
23
|
-
label: config[:label],
|
|
24
|
-
value: config[:value],
|
|
25
|
-
per_page: config[:per_page]
|
|
26
|
-
)
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
private
|
|
30
|
-
|
|
31
|
-
def tam_select_scope(config)
|
|
32
|
-
config[:model].all
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def search(records, config)
|
|
36
|
-
query = params[:q].to_s.strip
|
|
37
|
-
return records if query.blank?
|
|
38
|
-
|
|
39
|
-
model = config[:model]
|
|
40
|
-
pattern = "%#{model.sanitize_sql_like(query)}%"
|
|
41
|
-
predicates = config[:search_by].map { |field| model.arel_table[field].matches(pattern) }
|
|
42
|
-
records.where(predicates.reduce(&:or))
|
|
43
|
-
end
|
|
4
|
+
# Configure this base controller in a subclass, or include TamSelectRemote
|
|
5
|
+
# directly in an existing resource controller. See the Tam Select README.
|
|
44
6
|
end
|
data/lib/tam_select/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tam_select
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 1.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tamiru Hailu
|
|
@@ -62,6 +62,7 @@ files:
|
|
|
62
62
|
- lib/generators/tam_select/templates/tam_select_helper.rb
|
|
63
63
|
- lib/generators/tam_select/templates/tam_select_input.rb
|
|
64
64
|
- lib/generators/tam_select/templates/tam_select_paginatable.rb
|
|
65
|
+
- lib/generators/tam_select/templates/tam_select_remote.rb
|
|
65
66
|
- lib/generators/tam_select/templates/tam_select_remote_controller.rb
|
|
66
67
|
- lib/tam_select.rb
|
|
67
68
|
- lib/tam_select/engine.rb
|