activeadmin-tom_select 4.1.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.
- checksums.yaml +7 -0
- data/.actrc +20 -0
- data/.claude/commands/fix-tests.md +203 -0
- data/.github/workflows/ci.yml +174 -0
- data/.github/workflows/npm-publish.yml +50 -0
- data/.gitignore +35 -0
- data/.npmignore +58 -0
- data/.rspec +1 -0
- data/.rubocop.yml +75 -0
- data/.yardopts +2 -0
- data/AGENTS.md +39 -0
- data/Appraisals +9 -0
- data/CHANGELOG.md +64 -0
- data/CLAUDE.md +157 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +368 -0
- data/LICENSE.txt +25 -0
- data/README.md +483 -0
- data/Rakefile +4 -0
- data/activeadmin-tom_select.gemspec +43 -0
- data/bin/rspec +17 -0
- data/config/database.yml +16 -0
- data/docs/activeadmin-4-detailed-reference.md +932 -0
- data/docs/activeadmin-4-gem-migration-guide.md +313 -0
- data/docs/combustion.md +213 -0
- data/docs/fail.png +0 -0
- data/docs/guide-update-your-app.md +283 -0
- data/docs/normal.png +0 -0
- data/docs/propshaft-readme.md +320 -0
- data/docs/propshaft-upgrade.md +484 -0
- data/docs/setup-activeadmin-app.md +552 -0
- data/docs/setup-activeadmin-gem.md +535 -0
- data/docs/tailwind/blog-page.md +341 -0
- data/docs/tailwind/upgrade-guide-enhanced.md +438 -0
- data/docs/tailwind/upgrade-guide.md +416 -0
- data/docs/tailwind-4/active_admin.rake +38 -0
- data/docs/tailwind-4/active_admin.tailwind.css +415 -0
- data/docs/tailwind-4/tailwind-active_admin.config.js +18 -0
- data/docs/test-app-change.md +154 -0
- data/docs/test-environment-fixes.md +58 -0
- data/docs/update-tom-select.md +184 -0
- data/docs/upload-system.md +225 -0
- data/gemfiles/rails_7.x_active_admin_4.x.gemfile +10 -0
- data/gemfiles/rails_7.x_active_admin_4.x.gemfile.lock +377 -0
- data/gemfiles/rails_8.x_active_admin_4.x.gemfile +10 -0
- data/gemfiles/rails_8.x_active_admin_4.x.gemfile.lock +372 -0
- data/lefthook.yml +17 -0
- data/lib/activeadmin/inputs/filters/searchable_select_input.rb +19 -0
- data/lib/activeadmin/inputs/searchable_select_input.rb +16 -0
- data/lib/activeadmin/tom_select/engine.rb +17 -0
- data/lib/activeadmin/tom_select/option_collection.rb +128 -0
- data/lib/activeadmin/tom_select/resource_dsl_extension.rb +56 -0
- data/lib/activeadmin/tom_select/resource_extension.rb +10 -0
- data/lib/activeadmin/tom_select/select_input_extension.rb +168 -0
- data/lib/activeadmin/tom_select/version.rb +5 -0
- data/lib/activeadmin/tom_select.rb +20 -0
- data/lib/activeadmin-tom_select.rb +5 -0
- data/lib/generators/active_admin/tom_select/install/install_generator.rb +180 -0
- data/npm-package/package-lock.json +51 -0
- data/npm-package/package.json +43 -0
- data/npm-package/src/index.js +153 -0
- data/npm-package/src/tom-select-tailwind.css +392 -0
- data/sonar-project.properties +25 -0
- data/spec/features/ajax_params_spec.rb +31 -0
- data/spec/features/asset_pipeline_diagnostic_spec.rb +155 -0
- data/spec/features/end_to_end_spec.rb +273 -0
- data/spec/features/filter_input_spec.rb +144 -0
- data/spec/features/form_input_spec.rb +122 -0
- data/spec/features/inline_ajax_setting_spec.rb +26 -0
- data/spec/features/input_errors_spec.rb +76 -0
- data/spec/features/input_html_options_spec.rb +30 -0
- data/spec/features/options_dsl_spec.rb +230 -0
- data/spec/features/production_build_spec.rb +108 -0
- data/spec/internal/.node-version +1 -0
- data/spec/internal/Gemfile +43 -0
- data/spec/internal/Gemfile.lock +333 -0
- data/spec/internal/Procfile.dev +3 -0
- data/spec/internal/README.md +24 -0
- data/spec/internal/Rakefile +6 -0
- data/spec/internal/app/admin/categories.rb +26 -0
- data/spec/internal/app/admin/dashboard.rb +29 -0
- data/spec/internal/app/admin/option_types.rb +19 -0
- data/spec/internal/app/admin/option_values.rb +30 -0
- data/spec/internal/app/admin/posts.rb +27 -0
- data/spec/internal/app/admin/products.rb +22 -0
- data/spec/internal/app/admin/rgb_colors.rb +25 -0
- data/spec/internal/app/admin/tag_names.rb +21 -0
- data/spec/internal/app/admin/test_ajax_params_category.rb +10 -0
- data/spec/internal/app/admin/test_ajax_params_post.rb +20 -0
- data/spec/internal/app/admin/test_form_post_class.rb +7 -0
- data/spec/internal/app/admin/test_form_post_custom.rb +11 -0
- data/spec/internal/app/admin/test_form_post_resource.rb +11 -0
- data/spec/internal/app/admin/test_form_post_resource_custom.rb +12 -0
- data/spec/internal/app/admin/test_inline_ajax_post.rb +9 -0
- data/spec/internal/app/admin/test_input_html_post.rb +11 -0
- data/spec/internal/app/admin/test_posts_display_text.rb +9 -0
- data/spec/internal/app/admin/test_posts_filter.rb +9 -0
- data/spec/internal/app/admin/test_posts_named.rb +9 -0
- data/spec/internal/app/admin/test_posts_pagination.rb +9 -0
- data/spec/internal/app/admin/test_posts_payload_lambda.rb +11 -0
- data/spec/internal/app/admin/test_posts_payload_proc.rb +9 -0
- data/spec/internal/app/admin/test_posts_scope_lambda.rb +8 -0
- data/spec/internal/app/admin/test_posts_scope_params.rb +8 -0
- data/spec/internal/app/admin/test_posts_scope_user.rb +8 -0
- data/spec/internal/app/admin/test_posts_text_attr.rb +5 -0
- data/spec/internal/app/admin/users.rb +23 -0
- data/spec/internal/app/admin/variants.rb +31 -0
- data/spec/internal/app/assets/config/manifest.js +2 -0
- data/spec/internal/app/assets/images/.keep +0 -0
- data/spec/internal/app/assets/stylesheets/active_admin.tailwind.css +16 -0
- data/spec/internal/app/assets/stylesheets/application.tailwind.css +15 -0
- data/spec/internal/app/controllers/application_controller.rb +9 -0
- data/spec/internal/app/controllers/concerns/.keep +0 -0
- data/spec/internal/app/helpers/application_helper.rb +2 -0
- data/spec/internal/app/javascript/active_admin.js +19 -0
- data/spec/internal/app/javascript/application.js +2 -0
- data/spec/internal/app/jobs/application_job.rb +7 -0
- data/spec/internal/app/mailers/application_mailer.rb +4 -0
- data/spec/internal/app/models/admin_user.rb +9 -0
- data/spec/internal/app/models/application_record.rb +3 -0
- data/spec/internal/app/models/article.rb +12 -0
- data/spec/internal/app/models/category.rb +12 -0
- data/spec/internal/app/models/color.rb +9 -0
- data/spec/internal/app/models/concerns/.keep +0 -0
- data/spec/internal/app/models/internal/tag_name.rb +14 -0
- data/spec/internal/app/models/internal_tag_name.rb +11 -0
- data/spec/internal/app/models/option_type.rb +12 -0
- data/spec/internal/app/models/option_value.rb +4 -0
- data/spec/internal/app/models/post.rb +15 -0
- data/spec/internal/app/models/product.rb +12 -0
- data/spec/internal/app/models/rgb_color.rb +16 -0
- data/spec/internal/app/models/tag.rb +12 -0
- data/spec/internal/app/models/tagging.rb +12 -0
- data/spec/internal/app/models/user.rb +12 -0
- data/spec/internal/app/models/variant.rb +12 -0
- data/spec/internal/app/views/layouts/application.html.erb +28 -0
- data/spec/internal/app/views/layouts/mailer.html.erb +13 -0
- data/spec/internal/app/views/layouts/mailer.text.erb +1 -0
- data/spec/internal/app/views/pwa/manifest.json.erb +22 -0
- data/spec/internal/app/views/pwa/service-worker.js +26 -0
- data/spec/internal/bin/bundle +117 -0
- data/spec/internal/bin/dev +11 -0
- data/spec/internal/bin/rackup +27 -0
- data/spec/internal/bin/rails +4 -0
- data/spec/internal/bin/rake +4 -0
- data/spec/internal/bin/setup +37 -0
- data/spec/internal/config/application.rb +50 -0
- data/spec/internal/config/boot.rb +3 -0
- data/spec/internal/config/credentials.yml.enc +1 -0
- data/spec/internal/config/database.yml +32 -0
- data/spec/internal/config/environment.rb +5 -0
- data/spec/internal/config/environments/development.rb +63 -0
- data/spec/internal/config/environments/production.rb +86 -0
- data/spec/internal/config/environments/test.rb +50 -0
- data/spec/internal/config/initializers/active_admin.rb +54 -0
- data/spec/internal/config/initializers/assets.rb +8 -0
- data/spec/internal/config/initializers/content_security_policy.rb +25 -0
- data/spec/internal/config/initializers/devise.rb +315 -0
- data/spec/internal/config/initializers/filter_parameter_logging.rb +8 -0
- data/spec/internal/config/initializers/inflections.rb +16 -0
- data/spec/internal/config/initializers/searchable_select.rb +6 -0
- data/spec/internal/config/locales/devise.en.yml +65 -0
- data/spec/internal/config/locales/en.yml +31 -0
- data/spec/internal/config/master.key +1 -0
- data/spec/internal/config/puma.rb +38 -0
- data/spec/internal/config/routes.rb +17 -0
- data/spec/internal/config.ru +6 -0
- data/spec/internal/db/schema.rb +174 -0
- data/spec/internal/db/seeds.rb +167 -0
- data/spec/internal/esbuild.config.js +34 -0
- data/spec/internal/lib/tasks/.keep +0 -0
- data/spec/internal/lib/tasks/active_admin.rake +55 -0
- data/spec/internal/log/.keep +0 -0
- data/spec/internal/package-lock.json +1954 -0
- data/spec/internal/package.json +21 -0
- data/spec/internal/public/400.html +114 -0
- data/spec/internal/public/404.html +114 -0
- data/spec/internal/public/406-unsupported-browser.html +114 -0
- data/spec/internal/public/422.html +114 -0
- data/spec/internal/public/500.html +114 -0
- data/spec/internal/public/icon.png +0 -0
- data/spec/internal/public/icon.svg +3 -0
- data/spec/internal/public/robots.txt +1 -0
- data/spec/internal/script/.keep +0 -0
- data/spec/internal/storage/.keep +0 -0
- data/spec/internal/tailwind.config.js +23 -0
- data/spec/internal/vendor/.keep +0 -0
- data/spec/internal/yarn.lock +824 -0
- data/spec/rails_helper.rb +62 -0
- data/spec/spec_helper.rb +138 -0
- data/spec/support/active_admin_helpers.rb +17 -0
- data/spec/support/capybara.rb +8 -0
- data/spec/support/models.rb +11 -0
- data/spec/support/pluck_polyfill.rb +12 -0
- data/spec/support/reset_settings.rb +5 -0
- metadata +497 -0
@@ -0,0 +1,168 @@
|
|
1
|
+
module ActiveAdmin
|
2
|
+
module SearchableSelect
|
3
|
+
# Mixin for searchable select inputs.
|
4
|
+
#
|
5
|
+
# Supports the same options as inputs of type `:select`.
|
6
|
+
#
|
7
|
+
# Adds support for an `ajax` option to fetch options data from a
|
8
|
+
# JSON endpoint. Pass either `true` to use defaults or a hash
|
9
|
+
# containing some of the following options:
|
10
|
+
#
|
11
|
+
# - `resource`: ActiveRecord model class of ActiveAdmin resource
|
12
|
+
# which provides the collection action to fetch options
|
13
|
+
# from. By default the resource is auto detected via the name
|
14
|
+
# of the input attribute.
|
15
|
+
#
|
16
|
+
# - `collection_name`: Name passed to the
|
17
|
+
# `searchable_select_options` method that defines the collection
|
18
|
+
# action to fetch options from.
|
19
|
+
#
|
20
|
+
# - `params`: Hash of query parameters that shall be passed to the
|
21
|
+
# options endpoint.
|
22
|
+
#
|
23
|
+
# - `path_params`: Hash of parameters, which would be passed to the
|
24
|
+
# dynamic collection path generation for the resource.
|
25
|
+
# e.g `admin_articles_path(path_params)`
|
26
|
+
#
|
27
|
+
# If the `ajax` option is present, the `collection` option is
|
28
|
+
# ignored.
|
29
|
+
module SelectInputExtension
|
30
|
+
# @api private
|
31
|
+
def to_html
|
32
|
+
super
|
33
|
+
rescue RuntimeError => e
|
34
|
+
# In development/test, display the error message
|
35
|
+
raise e unless Rails.env.development? || Rails.env.test?
|
36
|
+
|
37
|
+
template.content_tag(:div, e.message, class: 'searchable-select-error')
|
38
|
+
end
|
39
|
+
|
40
|
+
# @api private
|
41
|
+
def input_html_options
|
42
|
+
super.tap do |options|
|
43
|
+
options[:class] = [options[:class], 'searchable-select-input'].compact.join(' ')
|
44
|
+
options['data-ajax-url'] = ajax_url if ajax? && !SearchableSelect.inline_ajax_options
|
45
|
+
options['data-clearable'] = true if clearable?
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# @api private
|
50
|
+
def collection_from_options
|
51
|
+
return super unless options[:ajax]
|
52
|
+
|
53
|
+
collection = if SearchableSelect.inline_ajax_options
|
54
|
+
all_options_collection
|
55
|
+
else
|
56
|
+
selected_value_collection
|
57
|
+
end
|
58
|
+
|
59
|
+
# Remove any empty/blank options since we use clear button instead
|
60
|
+
collection.reject { |item| item.first.to_s.strip.empty? && item.last.to_s.strip.empty? }
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def ajax?
|
66
|
+
options[:ajax].present?
|
67
|
+
end
|
68
|
+
|
69
|
+
def clearable?
|
70
|
+
# Default to true unless explicitly set to false
|
71
|
+
options.fetch(:clearable, true)
|
72
|
+
end
|
73
|
+
|
74
|
+
def ajax_url
|
75
|
+
return unless options[:ajax]
|
76
|
+
|
77
|
+
[ajax_resource.route_collection_path(path_params),
|
78
|
+
'/',
|
79
|
+
option_collection.collection_action_name,
|
80
|
+
'?',
|
81
|
+
ajax_params.to_query].join
|
82
|
+
end
|
83
|
+
|
84
|
+
def all_options_collection
|
85
|
+
option_collection_scope.all.map do |record|
|
86
|
+
option_for_record(record)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def selected_value_collection
|
91
|
+
selected_records.collect { |s| option_for_record(s) }
|
92
|
+
end
|
93
|
+
|
94
|
+
def option_for_record(record)
|
95
|
+
[option_collection.display_text(record), record.id]
|
96
|
+
end
|
97
|
+
|
98
|
+
def selected_records
|
99
|
+
@selected_records ||=
|
100
|
+
if selected_values
|
101
|
+
option_collection_scope.where(id: selected_values)
|
102
|
+
else
|
103
|
+
[]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def selected_values
|
108
|
+
@object&.send(input_name)
|
109
|
+
end
|
110
|
+
|
111
|
+
def option_collection_scope
|
112
|
+
option_collection.scope(template, path_params.merge(ajax_params))
|
113
|
+
end
|
114
|
+
|
115
|
+
def option_collection
|
116
|
+
ajax_resource
|
117
|
+
.searchable_select_option_collections
|
118
|
+
.fetch(ajax_option_collection_name) do
|
119
|
+
raise("No option collection named '#{ajax_option_collection_name}' " \
|
120
|
+
"defined in '#{ajax_resource_class.name}' admin.")
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def ajax_resource
|
125
|
+
@ajax_resource ||=
|
126
|
+
template.active_admin_namespace.resource_for(ajax_resource_class) ||
|
127
|
+
raise("No admin found for '#{ajax_resource_class.name}' to fetch " \
|
128
|
+
'options for searchable select input from.')
|
129
|
+
end
|
130
|
+
|
131
|
+
def ajax_resource_class
|
132
|
+
ajax_options.fetch(:resource) do
|
133
|
+
raise_cannot_auto_detect_resource unless reflection
|
134
|
+
reflection.klass
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def raise_cannot_auto_detect_resource
|
139
|
+
raise('Cannot auto detect resource to fetch options for searchable select input from. ' \
|
140
|
+
"Explicitly pass class of an ActiveAdmin resource:\n\n " \
|
141
|
+
"f.input(:custom_category,\n " \
|
142
|
+
"type: :searchable_select,\n " \
|
143
|
+
"ajax: {\n " \
|
144
|
+
"resource: Category\n " \
|
145
|
+
"})\n")
|
146
|
+
end
|
147
|
+
|
148
|
+
def ajax_option_collection_name
|
149
|
+
ajax_options.fetch(:collection_name, :all)
|
150
|
+
end
|
151
|
+
|
152
|
+
def ajax_params
|
153
|
+
ajax_options.fetch(:params, {})
|
154
|
+
end
|
155
|
+
|
156
|
+
def path_params
|
157
|
+
ajax_options.fetch(:path_params, {})
|
158
|
+
end
|
159
|
+
|
160
|
+
def ajax_options
|
161
|
+
# ActiveAdmin 4 may transform ajax hash to boolean
|
162
|
+
return {} if options[:ajax] == true || options[:ajax].nil?
|
163
|
+
|
164
|
+
options[:ajax]
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'activeadmin/tom_select/engine'
|
2
|
+
require 'activeadmin/tom_select/option_collection'
|
3
|
+
require 'activeadmin/tom_select/resource_extension'
|
4
|
+
require 'activeadmin/tom_select/resource_dsl_extension'
|
5
|
+
require 'activeadmin/tom_select/select_input_extension'
|
6
|
+
require 'activeadmin/tom_select/version'
|
7
|
+
|
8
|
+
ActiveAdmin::Resource.include ActiveAdmin::SearchableSelect::ResourceExtension
|
9
|
+
ActiveAdmin::ResourceDSL.include ActiveAdmin::SearchableSelect::ResourceDSLExtension
|
10
|
+
|
11
|
+
module ActiveAdmin
|
12
|
+
# Global settings for searchable selects
|
13
|
+
module SearchableSelect
|
14
|
+
# Statically render all options into searchable selects with
|
15
|
+
# `ajax` option set to true. This can be used to ease ui driven
|
16
|
+
# integration testing.
|
17
|
+
mattr_accessor :inline_ajax_options
|
18
|
+
self.inline_ajax_options = false
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
|
3
|
+
module ActiveAdmin
|
4
|
+
module TomSelect
|
5
|
+
module Generators
|
6
|
+
class InstallGenerator < Rails::Generators::Base
|
7
|
+
source_root File.expand_path('templates', __dir__)
|
8
|
+
|
9
|
+
desc 'Installs ActiveAdmin Tom Select for ActiveAdmin 4.x'
|
10
|
+
|
11
|
+
class_option :bundler,
|
12
|
+
type: :string,
|
13
|
+
default: 'esbuild',
|
14
|
+
desc: 'JavaScript bundler to use (esbuild, importmap, webpack)',
|
15
|
+
enum: %w[esbuild importmap webpack]
|
16
|
+
|
17
|
+
def install_npm_package
|
18
|
+
return unless options[:bundler] != 'importmap'
|
19
|
+
|
20
|
+
say 'Installing @rocket-sensei/activeadmin-tom_select npm package...', :green
|
21
|
+
run 'npm install @rocket-sensei/activeadmin-tom_select'
|
22
|
+
end
|
23
|
+
|
24
|
+
def setup_javascript
|
25
|
+
case options[:bundler]
|
26
|
+
when 'esbuild'
|
27
|
+
setup_esbuild
|
28
|
+
when 'importmap'
|
29
|
+
setup_importmap
|
30
|
+
when 'webpack'
|
31
|
+
setup_webpack
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def setup_stylesheets
|
36
|
+
say 'Tom Select styles are now included in the package', :green
|
37
|
+
end
|
38
|
+
|
39
|
+
def show_post_install_message
|
40
|
+
say "\n✅ ActiveAdmin Tom Select has been installed!", :green
|
41
|
+
|
42
|
+
case options[:bundler]
|
43
|
+
when 'esbuild'
|
44
|
+
say "\nMake sure to rebuild your JavaScript:", :yellow
|
45
|
+
say ' npm run build', :cyan
|
46
|
+
say "\nFor development with watch mode:", :yellow
|
47
|
+
say ' npm run build -- --watch', :cyan
|
48
|
+
when 'importmap'
|
49
|
+
say "\nRestart your Rails server to load the new pins.", :yellow
|
50
|
+
when 'webpack'
|
51
|
+
say "\nRecompile your webpack bundles:", :yellow
|
52
|
+
say ' bin/webpack', :cyan
|
53
|
+
end
|
54
|
+
|
55
|
+
say "\n📚 Usage example:", :green
|
56
|
+
say <<~RUBY
|
57
|
+
|
58
|
+
# In your ActiveAdmin resource:
|
59
|
+
ActiveAdmin.register User do
|
60
|
+
searchable_select_options(
|
61
|
+
scope: User.all,
|
62
|
+
text_attribute: :name
|
63
|
+
)
|
64
|
+
end
|
65
|
+
|
66
|
+
ActiveAdmin.register Post do
|
67
|
+
form do |f|
|
68
|
+
f.inputs do
|
69
|
+
f.input :user, as: :searchable_select, ajax: true
|
70
|
+
end
|
71
|
+
f.actions
|
72
|
+
end
|
73
|
+
end
|
74
|
+
RUBY
|
75
|
+
end
|
76
|
+
|
77
|
+
PACKAGE_JSON_FILE = 'package.json'.freeze
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def setup_esbuild
|
82
|
+
say 'Setting up for esbuild...', :green
|
83
|
+
|
84
|
+
# Check if app/javascript/active_admin.js exists
|
85
|
+
js_file = 'app/javascript/active_admin.js'
|
86
|
+
|
87
|
+
if File.exist?(js_file)
|
88
|
+
say "Adding tom_select to #{js_file}...", :green
|
89
|
+
append_to_file js_file do
|
90
|
+
<<~JS
|
91
|
+
|
92
|
+
// ActiveAdmin Tom Select
|
93
|
+
import '@rocket-sensei/activeadmin-tom_select';
|
94
|
+
JS
|
95
|
+
end
|
96
|
+
else
|
97
|
+
say "Creating #{js_file}...", :green
|
98
|
+
create_file js_file do
|
99
|
+
<<~JS
|
100
|
+
import "@activeadmin/activeadmin";
|
101
|
+
|
102
|
+
// ActiveAdmin Tom Select
|
103
|
+
import '@rocket-sensei/activeadmin-tom_select';
|
104
|
+
JS
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Update package.json scripts if needed
|
109
|
+
return unless File.exist?(PACKAGE_JSON_FILE)
|
110
|
+
|
111
|
+
package_json = JSON.parse(File.read(PACKAGE_JSON_FILE))
|
112
|
+
|
113
|
+
return if package_json['scripts'] && package_json['scripts']['build']
|
114
|
+
|
115
|
+
say 'Adding build script to package.json...', :green
|
116
|
+
package_json['scripts'] ||= {}
|
117
|
+
package_json['scripts']['build'] =
|
118
|
+
'esbuild app/javascript/*.* --bundle --sourcemap --format=esm ' \
|
119
|
+
'--outdir=app/assets/builds --public-path=/assets'
|
120
|
+
|
121
|
+
File.write(PACKAGE_JSON_FILE, JSON.pretty_generate(package_json))
|
122
|
+
end
|
123
|
+
|
124
|
+
def setup_importmap
|
125
|
+
say 'Setting up for importmap...', :green
|
126
|
+
|
127
|
+
# Add pins to importmap.rb
|
128
|
+
if File.exist?('config/importmap.rb')
|
129
|
+
say 'Adding pins to config/importmap.rb...', :green
|
130
|
+
append_to_file 'config/importmap.rb' do
|
131
|
+
<<~RUBY
|
132
|
+
|
133
|
+
# ActiveAdmin Tom Select
|
134
|
+
pin "activeadmin-tom_select", to: "activeadmin-tom_select.js"
|
135
|
+
RUBY
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Copy the vendor JavaScript file
|
140
|
+
say 'Copying vendor JavaScript file...', :green
|
141
|
+
copy_file '../../../../vendor/assets/javascripts/activeadmin-tom_select.js',
|
142
|
+
'app/assets/javascripts/activeadmin-tom_select.js'
|
143
|
+
|
144
|
+
# Update application.js
|
145
|
+
js_file = 'app/javascript/application.js'
|
146
|
+
return unless File.exist?(js_file)
|
147
|
+
|
148
|
+
say "Adding imports to #{js_file}...", :green
|
149
|
+
append_to_file js_file do
|
150
|
+
<<~JS
|
151
|
+
|
152
|
+
// ActiveAdmin Tom Select
|
153
|
+
import "activeadmin-tom_select"
|
154
|
+
JS
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def setup_webpack
|
159
|
+
say 'Setting up for webpack...', :green
|
160
|
+
|
161
|
+
js_file = 'app/javascript/packs/active_admin.js'
|
162
|
+
|
163
|
+
if File.exist?(js_file)
|
164
|
+
say "Adding tom_select to #{js_file}...", :green
|
165
|
+
append_to_file js_file do
|
166
|
+
<<~JS
|
167
|
+
|
168
|
+
// ActiveAdmin Tom Select
|
169
|
+
import '@rocket-sensei/activeadmin-tom_select';
|
170
|
+
JS
|
171
|
+
end
|
172
|
+
else
|
173
|
+
say 'Please manually add the tom_select import ' \
|
174
|
+
'to your ActiveAdmin JavaScript pack', :yellow
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
{
|
2
|
+
"name": "@rocket-sensei/activeadmin-tom_select",
|
3
|
+
"version": "4.1.0",
|
4
|
+
"lockfileVersion": 3,
|
5
|
+
"requires": true,
|
6
|
+
"packages": {
|
7
|
+
"": {
|
8
|
+
"name": "@rocket-sensei/activeadmin-tom_select",
|
9
|
+
"version": "4.1.0",
|
10
|
+
"license": "MIT",
|
11
|
+
"peerDependencies": {
|
12
|
+
"tom-select": "^2.4.3"
|
13
|
+
}
|
14
|
+
},
|
15
|
+
"node_modules/@orchidjs/sifter": {
|
16
|
+
"version": "1.1.0",
|
17
|
+
"resolved": "https://registry.npmjs.org/@orchidjs/sifter/-/sifter-1.1.0.tgz",
|
18
|
+
"integrity": "sha512-mYwHCfr736cIWWdhhSZvDbf90AKt2xyrJspKFC3qyIJG1LtrJeJunYEqCGG4Aq2ijENbc4WkOjszcvNaIAS/pQ==",
|
19
|
+
"license": "Apache-2.0",
|
20
|
+
"peer": true,
|
21
|
+
"dependencies": {
|
22
|
+
"@orchidjs/unicode-variants": "^1.1.2"
|
23
|
+
}
|
24
|
+
},
|
25
|
+
"node_modules/@orchidjs/unicode-variants": {
|
26
|
+
"version": "1.1.2",
|
27
|
+
"resolved": "https://registry.npmjs.org/@orchidjs/unicode-variants/-/unicode-variants-1.1.2.tgz",
|
28
|
+
"integrity": "sha512-5DobW1CHgnBROOEpFlEXytED5OosEWESFvg/VYmH0143oXcijYTprRYJTs+55HzGM4IqxiLFSuqEzu9mPNwVsA==",
|
29
|
+
"license": "Apache-2.0",
|
30
|
+
"peer": true
|
31
|
+
},
|
32
|
+
"node_modules/tom-select": {
|
33
|
+
"version": "2.4.3",
|
34
|
+
"resolved": "https://registry.npmjs.org/tom-select/-/tom-select-2.4.3.tgz",
|
35
|
+
"integrity": "sha512-MFFrMxP1bpnAMPbdvPCZk0KwYxLqhYZso39torcdoefeV/NThNyDu8dV96/INJ5XQVTL3O55+GqQ78Pkj5oCfw==",
|
36
|
+
"license": "Apache-2.0",
|
37
|
+
"peer": true,
|
38
|
+
"dependencies": {
|
39
|
+
"@orchidjs/sifter": "^1.1.0",
|
40
|
+
"@orchidjs/unicode-variants": "^1.1.2"
|
41
|
+
},
|
42
|
+
"engines": {
|
43
|
+
"node": "*"
|
44
|
+
},
|
45
|
+
"funding": {
|
46
|
+
"type": "opencollective",
|
47
|
+
"url": "https://opencollective.com/tom-select"
|
48
|
+
}
|
49
|
+
}
|
50
|
+
}
|
51
|
+
}
|
@@ -0,0 +1,43 @@
|
|
1
|
+
{
|
2
|
+
"name": "@rocket-sensei/activeadmin-tom_select",
|
3
|
+
"version": "4.1.0",
|
4
|
+
"description": "Use Tom Select for searchable selects in Active Admin forms and filters.",
|
5
|
+
"main": "src/index.js",
|
6
|
+
"module": "src/index.js",
|
7
|
+
"style": "src/tom-select-tailwind.css",
|
8
|
+
"exports": {
|
9
|
+
".": {
|
10
|
+
"import": "./src/index.js",
|
11
|
+
"require": "./src/index.js",
|
12
|
+
"default": "./src/index.js"
|
13
|
+
},
|
14
|
+
"./css": "./src/tom-select-tailwind.css",
|
15
|
+
"./style": "./src/tom-select-tailwind.css"
|
16
|
+
},
|
17
|
+
"repository": {
|
18
|
+
"type": "git",
|
19
|
+
"url": "git+https://github.com/rs-pro/activeadmin-tom_select.git"
|
20
|
+
},
|
21
|
+
"author": "Rocket Sensei <info@rs.pro>",
|
22
|
+
"license": "MIT",
|
23
|
+
"private": false,
|
24
|
+
"bugs": {
|
25
|
+
"url": "https://github.com/rs-pro/activeadmin-tom_select/issues"
|
26
|
+
},
|
27
|
+
"homepage": "https://github.com/rs-pro/activeadmin-tom_select#readme",
|
28
|
+
"keywords": [
|
29
|
+
"tom-select",
|
30
|
+
"active",
|
31
|
+
"admin",
|
32
|
+
"searchable",
|
33
|
+
"select",
|
34
|
+
"activeadmin",
|
35
|
+
"rails"
|
36
|
+
],
|
37
|
+
"peerDependencies": {
|
38
|
+
"tom-select": "^2.4.3"
|
39
|
+
},
|
40
|
+
"files": [
|
41
|
+
"src/**/*"
|
42
|
+
]
|
43
|
+
}
|
@@ -0,0 +1,153 @@
|
|
1
|
+
// ES Module version for ActiveAdmin 4+ with esbuild/webpack
|
2
|
+
// Tom Select version - no jQuery dependency required
|
3
|
+
import TomSelect from 'tom-select';
|
4
|
+
|
5
|
+
const MODULE_NAME = 'ActiveAdmin Searchable Select';
|
6
|
+
const SELECTOR = '.searchable-select-input';
|
7
|
+
const INITIALIZED_CLASS = 'tom-select-initialized';
|
8
|
+
|
9
|
+
// Core initialization function
|
10
|
+
export function initSearchableSelects(inputs, extra) {
|
11
|
+
if (!inputs || inputs.length === 0) return;
|
12
|
+
|
13
|
+
// Handle both NodeList and array of elements
|
14
|
+
const elements = inputs instanceof NodeList ? Array.from(inputs) :
|
15
|
+
inputs.length !== undefined ? inputs : [inputs];
|
16
|
+
|
17
|
+
elements.forEach(element => {
|
18
|
+
// Skip if already initialized
|
19
|
+
if (element.classList.contains(INITIALIZED_CLASS)) return;
|
20
|
+
|
21
|
+
// Mark as initialized
|
22
|
+
element.classList.add(INITIALIZED_CLASS);
|
23
|
+
|
24
|
+
// Get options from data attributes
|
25
|
+
const dataOptions = element.dataset.searchableSelect ?
|
26
|
+
JSON.parse(element.dataset.searchableSelect) : {};
|
27
|
+
|
28
|
+
// Merge with extra options
|
29
|
+
const options = Object.assign({}, extra || {}, dataOptions);
|
30
|
+
|
31
|
+
// Configure AJAX if URL is provided
|
32
|
+
const ajaxUrl = element.dataset.ajaxUrl;
|
33
|
+
if (ajaxUrl) {
|
34
|
+
// Configure virtual scroll for pagination
|
35
|
+
options.plugins = options.plugins || [];
|
36
|
+
if (!options.plugins.includes('virtual_scroll')) {
|
37
|
+
options.plugins.push('virtual_scroll');
|
38
|
+
}
|
39
|
+
|
40
|
+
// Set max options for virtual scroll
|
41
|
+
options.maxOptions = options.maxOptions || 200;
|
42
|
+
|
43
|
+
// Configure the first URL for pagination
|
44
|
+
options.firstUrl = function(query) {
|
45
|
+
const url = new URL(ajaxUrl, window.location.href);
|
46
|
+
url.searchParams.set('term', query);
|
47
|
+
url.searchParams.set('page', 1);
|
48
|
+
return url.toString();
|
49
|
+
};
|
50
|
+
|
51
|
+
// Main load function with pagination support
|
52
|
+
options.load = function(query, callback) {
|
53
|
+
// Get the appropriate URL (either first or next)
|
54
|
+
const url = this.getUrl(query);
|
55
|
+
|
56
|
+
fetch(url)
|
57
|
+
.then(response => response.json())
|
58
|
+
.then(json => {
|
59
|
+
// Handle pagination info if present
|
60
|
+
if (json.pagination && json.pagination.more) {
|
61
|
+
// Set up the next URL for virtual scroll (1-based pagination)
|
62
|
+
const nextUrl = new URL(ajaxUrl, window.location.href);
|
63
|
+
nextUrl.searchParams.set('term', query);
|
64
|
+
// Backend now uses 1-based pagination and returns current page
|
65
|
+
const nextPage = (json.pagination.current || 1) + 1;
|
66
|
+
nextUrl.searchParams.set('page', nextPage);
|
67
|
+
this.setNextUrl(query, nextUrl.toString());
|
68
|
+
}
|
69
|
+
|
70
|
+
callback(json.results || json);
|
71
|
+
})
|
72
|
+
.catch(() => callback());
|
73
|
+
};
|
74
|
+
|
75
|
+
// Map Select2-style options to Tom Select
|
76
|
+
options.valueField = options.valueField || 'id';
|
77
|
+
options.labelField = options.labelField || 'text';
|
78
|
+
options.searchField = options.searchField || ['text'];
|
79
|
+
|
80
|
+
// Enable remote loading features
|
81
|
+
options.preload = options.preload !== false ? 'focus' : false;
|
82
|
+
options.loadThrottle = options.loadThrottle || 300;
|
83
|
+
}
|
84
|
+
|
85
|
+
// Handle placeholder
|
86
|
+
if (element.placeholder) {
|
87
|
+
options.placeholder = element.placeholder;
|
88
|
+
}
|
89
|
+
|
90
|
+
// Check if element should be clearable (default to true for searchable selects)
|
91
|
+
const isClearable = element.dataset.clearable !== 'false';
|
92
|
+
|
93
|
+
// Map common Select2 options to Tom Select equivalents
|
94
|
+
if (options.allowClear || isClearable) {
|
95
|
+
// Don't add empty option - we use clear_button plugin instead
|
96
|
+
// options.allowEmptyOption = true;
|
97
|
+
|
98
|
+
// Add clear_button plugin (make sure plugins array exists)
|
99
|
+
options.plugins = options.plugins || [];
|
100
|
+
if (!options.plugins.includes('clear_button')) {
|
101
|
+
options.plugins.push('clear_button');
|
102
|
+
}
|
103
|
+
}
|
104
|
+
|
105
|
+
if (options.minimumInputLength) {
|
106
|
+
options.shouldLoad = function(query) {
|
107
|
+
return query.length >= options.minimumInputLength;
|
108
|
+
};
|
109
|
+
}
|
110
|
+
|
111
|
+
// Initialize Tom Select
|
112
|
+
new TomSelect(element, options);
|
113
|
+
});
|
114
|
+
}
|
115
|
+
|
116
|
+
// Auto-initialize on common events
|
117
|
+
export function setupAutoInit() {
|
118
|
+
// Initialize on DOM ready
|
119
|
+
if (document.readyState === 'loading') {
|
120
|
+
document.addEventListener('DOMContentLoaded', function() {
|
121
|
+
initSearchableSelects(document.querySelectorAll(SELECTOR));
|
122
|
+
});
|
123
|
+
} else {
|
124
|
+
initSearchableSelects(document.querySelectorAll(SELECTOR));
|
125
|
+
}
|
126
|
+
|
127
|
+
// Support Turbo (Rails 7+)
|
128
|
+
document.addEventListener('turbo:load', function() {
|
129
|
+
initSearchableSelects(document.querySelectorAll(`${SELECTOR}:not(.${INITIALIZED_CLASS})`));
|
130
|
+
});
|
131
|
+
|
132
|
+
// ActiveAdmin 4 uses .has-many-add button click for dynamic content
|
133
|
+
document.addEventListener('click', function(event) {
|
134
|
+
if (event.target.closest('.has-many-add')) {
|
135
|
+
setTimeout(function() {
|
136
|
+
initSearchableSelects(
|
137
|
+
document.querySelectorAll(`${SELECTOR}:not(.${INITIALIZED_CLASS})`)
|
138
|
+
);
|
139
|
+
}, 10);
|
140
|
+
}
|
141
|
+
});
|
142
|
+
|
143
|
+
// Support has_many_add:after event (ActiveAdmin specific)
|
144
|
+
document.addEventListener('has_many_add:after', function(event) {
|
145
|
+
const fieldset = event.detail || event.target;
|
146
|
+
if (fieldset) {
|
147
|
+
const selects = fieldset.querySelectorAll(`${SELECTOR}:not(.${INITIALIZED_CLASS})`);
|
148
|
+
initSearchableSelects(selects);
|
149
|
+
}
|
150
|
+
});
|
151
|
+
|
152
|
+
console.log(`${MODULE_NAME} (Tom Select) initialized`);
|
153
|
+
}
|