administrate_filterable 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 655e44dca6e45d18592d84ccc20dbc79f47da6f79e5c7995c32cc2553f8d1a29
4
+ data.tar.gz: 05d8c8e708c9d6a08a1a917938f5c26b67e49872374b08206e4167de82b17721
5
+ SHA512:
6
+ metadata.gz: d4c07a5076e76713970cdacad3fa7b90a56cefe00a4aa4fa385f20389642c39e284ea72a9528d6f9111b422f45ad197517269d5c4c6ceb6f574c811914ea9a6b
7
+ data.tar.gz: 6f2a3adc9344bb6014f9a61b1048f012a87d60418d363df6e9f40a9caa2df2d67fa95f574d6d4af641ecc271fcd762942b18a37075ef8b9c59fccca29e94afe5
@@ -0,0 +1,20 @@
1
+ ---
2
+ name: CI
3
+ on: [push,pull_request]
4
+
5
+ jobs:
6
+ test:
7
+ runs-on: ubuntu-latest
8
+
9
+ steps:
10
+ - run: ls /opt/hostedtoolcache/Ruby
11
+ - uses: actions/checkout@v2
12
+ - name: Set up Ruby
13
+ uses: ruby/setup-ruby@master
14
+ with:
15
+ ruby-version: 3.2.2
16
+
17
+ - name: Install dependencies
18
+ run: bundle install
19
+ - name: Run tests
20
+ run: bundle exec rspec
data/.gitignore ADDED
@@ -0,0 +1,20 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+
13
+
14
+ # Ignore the default SQLite database.
15
+ spec/dummy/db/*.sqlite3
16
+ spec/dummy/db/*.sqlite3-journal
17
+
18
+ # Ignore all logfiles and tempfiles.
19
+ spec/dummy/log/*
20
+ spec/dummy/tmp/*
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/CHANGELOG ADDED
@@ -0,0 +1,5 @@
1
+ # CHANGELOG
2
+
3
+ ## 0.0.1
4
+
5
+ * Initial release with basic filter functionality
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ eval_gemfile File.join(File.dirname(__FILE__), "spec/dummy/Gemfile")
data/Gemfile.lock ADDED
@@ -0,0 +1,238 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ administrate_filterable (0.0.2)
5
+ actionview (>= 5.2.2.1)
6
+ administrate (>= 0.17.0)
7
+ rails (>= 4.2)
8
+ railties (>= 5.2.2.1)
9
+
10
+ GEM
11
+ remote: https://rubygems.org/
12
+ specs:
13
+ actioncable (7.0.4.3)
14
+ actionpack (= 7.0.4.3)
15
+ activesupport (= 7.0.4.3)
16
+ nio4r (~> 2.0)
17
+ websocket-driver (>= 0.6.1)
18
+ actionmailbox (7.0.4.3)
19
+ actionpack (= 7.0.4.3)
20
+ activejob (= 7.0.4.3)
21
+ activerecord (= 7.0.4.3)
22
+ activestorage (= 7.0.4.3)
23
+ activesupport (= 7.0.4.3)
24
+ mail (>= 2.7.1)
25
+ net-imap
26
+ net-pop
27
+ net-smtp
28
+ actionmailer (7.0.4.3)
29
+ actionpack (= 7.0.4.3)
30
+ actionview (= 7.0.4.3)
31
+ activejob (= 7.0.4.3)
32
+ activesupport (= 7.0.4.3)
33
+ mail (~> 2.5, >= 2.5.4)
34
+ net-imap
35
+ net-pop
36
+ net-smtp
37
+ rails-dom-testing (~> 2.0)
38
+ actionpack (7.0.4.3)
39
+ actionview (= 7.0.4.3)
40
+ activesupport (= 7.0.4.3)
41
+ rack (~> 2.0, >= 2.2.0)
42
+ rack-test (>= 0.6.3)
43
+ rails-dom-testing (~> 2.0)
44
+ rails-html-sanitizer (~> 1.0, >= 1.2.0)
45
+ actiontext (7.0.4.3)
46
+ actionpack (= 7.0.4.3)
47
+ activerecord (= 7.0.4.3)
48
+ activestorage (= 7.0.4.3)
49
+ activesupport (= 7.0.4.3)
50
+ globalid (>= 0.6.0)
51
+ nokogiri (>= 1.8.5)
52
+ actionview (7.0.4.3)
53
+ activesupport (= 7.0.4.3)
54
+ builder (~> 3.1)
55
+ erubi (~> 1.4)
56
+ rails-dom-testing (~> 2.0)
57
+ rails-html-sanitizer (~> 1.1, >= 1.2.0)
58
+ activejob (7.0.4.3)
59
+ activesupport (= 7.0.4.3)
60
+ globalid (>= 0.3.6)
61
+ activemodel (7.0.4.3)
62
+ activesupport (= 7.0.4.3)
63
+ activerecord (7.0.4.3)
64
+ activemodel (= 7.0.4.3)
65
+ activesupport (= 7.0.4.3)
66
+ activestorage (7.0.4.3)
67
+ actionpack (= 7.0.4.3)
68
+ activejob (= 7.0.4.3)
69
+ activerecord (= 7.0.4.3)
70
+ activesupport (= 7.0.4.3)
71
+ marcel (~> 1.0)
72
+ mini_mime (>= 1.1.0)
73
+ activesupport (7.0.4.3)
74
+ concurrent-ruby (~> 1.0, >= 1.0.2)
75
+ i18n (>= 1.6, < 2)
76
+ minitest (>= 5.1)
77
+ tzinfo (~> 2.0)
78
+ administrate (0.17.0)
79
+ actionpack (>= 5.0)
80
+ actionview (>= 5.0)
81
+ activerecord (>= 5.0)
82
+ datetime_picker_rails (~> 0.0.7)
83
+ jquery-rails (>= 4.0)
84
+ kaminari (>= 1.0)
85
+ momentjs-rails (~> 2.8)
86
+ sassc-rails (~> 2.1)
87
+ selectize-rails (~> 0.6)
88
+ builder (3.2.4)
89
+ byebug (11.1.3)
90
+ concurrent-ruby (1.1.10)
91
+ crass (1.0.6)
92
+ date (3.3.4)
93
+ datetime_picker_rails (0.0.7)
94
+ momentjs-rails (>= 2.8.1)
95
+ diff-lcs (1.5.0)
96
+ erubi (1.10.0)
97
+ ffi (1.15.5)
98
+ globalid (1.2.1)
99
+ activesupport (>= 6.1)
100
+ i18n (1.10.0)
101
+ concurrent-ruby (~> 1.0)
102
+ jquery-rails (4.4.0)
103
+ rails-dom-testing (>= 1, < 3)
104
+ railties (>= 4.2.0)
105
+ thor (>= 0.14, < 2.0)
106
+ kaminari (1.2.2)
107
+ activesupport (>= 4.1.0)
108
+ kaminari-actionview (= 1.2.2)
109
+ kaminari-activerecord (= 1.2.2)
110
+ kaminari-core (= 1.2.2)
111
+ kaminari-actionview (1.2.2)
112
+ actionview
113
+ kaminari-core (= 1.2.2)
114
+ kaminari-activerecord (1.2.2)
115
+ activerecord
116
+ kaminari-core (= 1.2.2)
117
+ kaminari-core (1.2.2)
118
+ loofah (2.18.0)
119
+ crass (~> 1.0.2)
120
+ nokogiri (>= 1.5.9)
121
+ mail (2.8.1)
122
+ mini_mime (>= 0.1.1)
123
+ net-imap
124
+ net-pop
125
+ net-smtp
126
+ marcel (1.0.2)
127
+ method_source (1.0.0)
128
+ mini_mime (1.1.5)
129
+ mini_portile2 (2.8.0)
130
+ minitest (5.15.0)
131
+ momentjs-rails (2.29.1.1)
132
+ railties (>= 3.1)
133
+ net-imap (0.4.4)
134
+ date
135
+ net-protocol
136
+ net-pop (0.1.2)
137
+ net-protocol
138
+ net-protocol (0.2.2)
139
+ timeout
140
+ net-smtp (0.4.0)
141
+ net-protocol
142
+ nio4r (2.5.9)
143
+ nokogiri (1.13.6)
144
+ mini_portile2 (~> 2.8.0)
145
+ racc (~> 1.4)
146
+ racc (1.6.0)
147
+ rack (2.2.3)
148
+ rack-test (1.1.0)
149
+ rack (>= 1.0, < 3)
150
+ rails (7.0.4.3)
151
+ actioncable (= 7.0.4.3)
152
+ actionmailbox (= 7.0.4.3)
153
+ actionmailer (= 7.0.4.3)
154
+ actionpack (= 7.0.4.3)
155
+ actiontext (= 7.0.4.3)
156
+ actionview (= 7.0.4.3)
157
+ activejob (= 7.0.4.3)
158
+ activemodel (= 7.0.4.3)
159
+ activerecord (= 7.0.4.3)
160
+ activestorage (= 7.0.4.3)
161
+ activesupport (= 7.0.4.3)
162
+ bundler (>= 1.15.0)
163
+ railties (= 7.0.4.3)
164
+ rails-dom-testing (2.0.3)
165
+ activesupport (>= 4.2.0)
166
+ nokogiri (>= 1.6)
167
+ rails-html-sanitizer (1.4.2)
168
+ loofah (~> 2.3)
169
+ railties (7.0.4.3)
170
+ actionpack (= 7.0.4.3)
171
+ activesupport (= 7.0.4.3)
172
+ method_source
173
+ rake (>= 12.2)
174
+ thor (~> 1.0)
175
+ zeitwerk (~> 2.5)
176
+ rake (13.0.6)
177
+ rb-readline (0.5.5)
178
+ rspec-core (3.9.3)
179
+ rspec-support (~> 3.9.3)
180
+ rspec-expectations (3.9.4)
181
+ diff-lcs (>= 1.2.0, < 2.0)
182
+ rspec-support (~> 3.9.0)
183
+ rspec-mocks (3.9.1)
184
+ diff-lcs (>= 1.2.0, < 2.0)
185
+ rspec-support (~> 3.9.0)
186
+ rspec-rails (3.9.1)
187
+ actionpack (>= 3.0)
188
+ activesupport (>= 3.0)
189
+ railties (>= 3.0)
190
+ rspec-core (~> 3.9.0)
191
+ rspec-expectations (~> 3.9.0)
192
+ rspec-mocks (~> 3.9.0)
193
+ rspec-support (~> 3.9.0)
194
+ rspec-support (3.9.4)
195
+ sassc (2.4.0)
196
+ ffi (~> 1.9)
197
+ sassc-rails (2.1.2)
198
+ railties (>= 4.0.0)
199
+ sassc (>= 2.0)
200
+ sprockets (> 3.0)
201
+ sprockets-rails
202
+ tilt
203
+ selectize-rails (0.12.6)
204
+ sprockets (4.0.3)
205
+ concurrent-ruby (~> 1.0)
206
+ rack (> 1, < 3)
207
+ sprockets-rails (3.4.2)
208
+ actionpack (>= 5.2)
209
+ activesupport (>= 5.2)
210
+ sprockets (>= 3.0.0)
211
+ sqlite3 (1.4.2)
212
+ thor (1.2.1)
213
+ tilt (2.0.10)
214
+ timeout (0.4.1)
215
+ tzinfo (2.0.6)
216
+ concurrent-ruby (~> 1.0)
217
+ websocket-driver (0.7.6)
218
+ websocket-extensions (>= 0.1.0)
219
+ websocket-extensions (0.1.5)
220
+ zeitwerk (2.6.12)
221
+
222
+ PLATFORMS
223
+ ruby
224
+
225
+ DEPENDENCIES
226
+ administrate (~> 0.17.0)
227
+ administrate_filterable!
228
+ byebug
229
+ rails (~> 7.0.0)
230
+ rb-readline
231
+ rspec-rails (~> 3.8)
232
+ sqlite3
233
+
234
+ RUBY VERSION
235
+ ruby 3.2.2p53
236
+
237
+ BUNDLED WITH
238
+ 2.4.22
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Irvan Fauziansyah
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,120 @@
1
+ # Administrate Custom Filter
2
+
3
+ ![Gem](https://img.shields.io/gem/v/administrate_filterable.svg)
4
+ ![CI](https://github.com/IrvanFza/administrate_filterable/workflows/CI/badge.svg)
5
+
6
+ An [Administrate](https://github.com/thoughtbot/administrate/) plugin to add custom filter in your index page. Highly inspired by [ActiveAdmin](https://github.com/activeadmin/activeadmin)'s filter.
7
+
8
+ ## Why you need this?
9
+
10
+ Let's agree that the Administrate's team has done a great job with the default search or filter functionality. It's simple and easy to use. It supports multiple search fields, cross relation search, and it's easy to customize (like enable/disable the search, defining custom search fields, etc).
11
+
12
+ But there are some drawbacks that I found:
13
+ 1. Since it uses single search box, the search process behind it is quite heavy. It will search all the attributes of the model, and it will be slower if you have a lot of data.
14
+ 2. Again, because it search all the attributes, it will be hard to search for a specific attribute. For example, if you have `registration_status` and `employment_status`, and you want to search for "active" registration status only, you will get all the "active" records, including the employment status.
15
+ 3. It's not user friendly when it comes to defined search list (e.g. dropdown list). The user will have to type the value manually, and it's not good for the user experience.
16
+
17
+ Please share your thoughts if you have any other reasons.
18
+
19
+ ## Requirements
20
+
21
+ - Ruby on Rails version >= 5.0
22
+ - Administrate version >= 0.2.2
23
+
24
+ ## Installation
25
+
26
+ Add `administrate_filterable` to your Gemfile:
27
+
28
+ ```ruby
29
+ gem 'administrate_filterable'
30
+ ```
31
+
32
+ And then execute:
33
+ ```
34
+ $ bundle install
35
+ ```
36
+
37
+ Or, simply just run:
38
+ ```
39
+ $ bundle add administrate_filterable
40
+ ```
41
+
42
+ ## Usage
43
+
44
+ For each resource you want to add custom filter, add the following line to the their respective Administrate controller.
45
+ ```ruby
46
+ include AdministrateFilterable::Filterer
47
+ ```
48
+
49
+ Example:
50
+ ```ruby
51
+ class UsersController < Administrate::ApplicationController
52
+ include AdministrateFilterable::Filterer
53
+
54
+ # ...
55
+ end
56
+ ```
57
+
58
+ By default all the attributes from COLLECTION_ATTRIBUTES will be rendered as the filter fields. You can override this by adding FILTER_ATTRIBUTES to your Administrate's dashboard file.
59
+
60
+ Example (`app/dashboards/user_dashboard.rb`):
61
+ ```ruby
62
+ class UserDashboard < Administrate::BaseDashboard
63
+ # ..
64
+ FILTER_ATTRIBUTES = [
65
+ :first_name,
66
+ :last_name,
67
+ :email,
68
+ :created_at
69
+ ].freeze
70
+ # ..
71
+ end
72
+ ```
73
+
74
+ By default this gem will add a filter button to the partial `views/admin/application/_index_header.html.erb`. But if you have your own Administrate index views or override that partial in your application you can add the button manually by adding the following line:
75
+ ```erb
76
+ <%= render('index_filter', page: page) %>
77
+ ```
78
+
79
+ Example (`app/views/admin/users/_index_header.html.erb`):
80
+ ```erb
81
+ ... other code here ...
82
+ <header class="main-content__header">
83
+ ... other code here ...
84
+
85
+ <%= render('index_filter', page: page) %>
86
+ </header>
87
+ ```
88
+
89
+ If you use assets pipeline, you need to include this gem's assets to your `app/assets/config/manifest.js` file:
90
+ ```javascript
91
+ // ... other code here ...
92
+
93
+ //= link administrate_filterable/application.css
94
+ //= link administrate_filterable/application.js
95
+ ```
96
+
97
+ ## To Do
98
+ There are still a lot of things to do to make this gem better. Here are some of them (sorted highest priority first):
99
+ - [ ] Add support for relational filter (e.g. filter by `belongs_to` association, etc)
100
+ - [ ] Add support to customize the dropdown list (e.g. add `prompt` option, add `include_blank` option, etc)
101
+ - [ ] Exclude checkbox, radio, or select value from the filter params if no action is performed on them
102
+ - [ ] Figure out a better way to implement the filter functionality (currently I override the `scoped_resource` method)
103
+ - [ ] Figure out a better way to pass the filter attributes to the form (currently I use instance variable in the overridden `scoped_resource` method)
104
+ - [ ] Add capability to customize the filter behavior (e.g. search by exact match, search by partial match, etc just like in the ActiveAdmin filter)
105
+ - [ ] Improve the toggle button user experience (e.g. add open/close animation, add dynamic open/close title, etc)
106
+
107
+ ## Contributing
108
+
109
+ 1. Contribution are welcome (codes, suggestions, and bugs)
110
+ 2. Please test your code: `bundle exec rspec`
111
+ 3. Please document your code
112
+
113
+ ## License
114
+
115
+ [MIT License](https://github.com/IrvanFza/administrate_filterable/blob/master/LICENSE)
116
+
117
+ ## Credits
118
+ Huge thanks for the following resources that help me a lot in creating this gem:
119
+ - [administrate_filterable](https://github.com/SourceLabsLLC/administrate_exportable): Basically I just copy the code from this gem and modify it to suit my needs, highly recommended if you need to export your data from Administrate.
120
+ - [Off-Canvas Menu](https://web.archive.org/web/20210304195120/https://codepen.io/11bits/pen/jryEGW): I found this CodePen from Google Images when I was looking for a way to create an off-canvas menu. I modified it a little bit to suit my needs. But it seems the codepen is no longer available, so I put the archived version here. If any of you know the original author, please let me know so I can give the proper credit.
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,40 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "administrate_filterable/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "administrate_filterable"
8
+ spec.version = AdministrateFilterable::VERSION
9
+ spec.authors = ["Irvan Fauziansyah"]
10
+ spec.email = ["ervhan@gmail.com"]
11
+ spec.homepage = 'https://github.com/IrvanFza/administrate_filterable'
12
+ spec.summary = "Administrate Custom Filter"
13
+ spec.description = "Simple plugin to add custom filter functionality to your Administrate index page."
14
+ spec.license = "MIT"
15
+
16
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
17
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
18
+ # if spec.respond_to?(:metadata)
19
+ # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
20
+ # else
21
+ # raise "RubyGems 2.0 or newer is required to protect against " \
22
+ # "public gem pushes."
23
+ # end
24
+
25
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
26
+ f.match(%r{^(test|spec|features)/})
27
+ end
28
+ spec.bindir = "exe"
29
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ["lib"]
31
+
32
+ spec.add_development_dependency "bundler", "~> 1.16"
33
+ spec.add_development_dependency "rake", "~> 10.0"
34
+ spec.add_development_dependency "rspec", "~> 3.0"
35
+ spec.add_development_dependency "byebug", "~> 10.0"
36
+ spec.add_dependency 'administrate','>= 0.17.0'
37
+ spec.add_dependency 'rails', '>= 4.2'
38
+ spec.add_dependency 'actionview', '>= 5.2.2.1'
39
+ spec.add_dependency 'railties', '>= 5.2.2.1'
40
+ end
@@ -0,0 +1,107 @@
1
+ const navTrigger = document.getElementsByClassName('administrate-filterable__toggle-button')[0];
2
+ const body = document.getElementsByTagName('body')[0];
3
+
4
+ navTrigger.addEventListener('click', toggleNavigation);
5
+
6
+ function toggleNavigation(event) {
7
+ event.preventDefault();
8
+ body.classList.toggle('administrate-filterable__open');
9
+ }
10
+
11
+ const applyFilterButton = document.getElementsByClassName('administrate-filterable__apply-filter')[0];
12
+ const form = document.getElementsByClassName('administrate-filterable__form')[0];
13
+
14
+ applyFilterButton.addEventListener('click', function(event) {
15
+ // Prevent form submission
16
+ event.preventDefault();
17
+
18
+ // Instantiate a new FormData object
19
+ let formData = new FormData(form);
20
+
21
+ // Create URLSearchParams from FormData
22
+ let formDataParams = new URLSearchParams([...formData.entries()]);
23
+
24
+ // get existing query params
25
+ let existingParams = new URLSearchParams(window.location.search);
26
+
27
+ // Get keys of existingParams
28
+ let keys = [...existingParams.keys()];
29
+
30
+ // delete keys from existingParams which don't exist in formDataParams
31
+ keys.forEach((key) => {
32
+ if (!formDataParams.has(key)) {
33
+ existingParams.delete(key);
34
+ }
35
+ });
36
+
37
+ // append or replace new formDataParams
38
+ formDataParams.forEach((value, key) => {
39
+ if (value.trim() !== '') {
40
+ existingParams.set(key, value);
41
+ } else {
42
+ existingParams.delete(key);
43
+ }
44
+ });
45
+
46
+ // Create query params string from existingParams
47
+ let queryParamsString = '?' + existingParams.toString();
48
+
49
+ // Get the URL without query params
50
+ const currentUrl = window.location.href.split('?')[0];
51
+
52
+ // Construct new URL with updated parameters
53
+ const newUrl = currentUrl + queryParamsString;
54
+
55
+ // Redirect to current url with new query params
56
+ window.location.href = newUrl;
57
+ });
58
+
59
+ window.onload = function() {
60
+ // Get form fields
61
+ const form = document.getElementsByClassName('administrate-filterable__form')[0];
62
+ let formData = new FormData(form);
63
+
64
+ // Extract query params from URL
65
+ const queryParams = new URLSearchParams(window.location.search);
66
+
67
+ // Loop through the query parameters and set form field values
68
+ for (let pair of queryParams.entries()) {
69
+ const formFields = document.getElementsByName(pair[0]);
70
+ const fieldValue = decodeURIComponent(pair[1]);
71
+ for(let field of formFields) {
72
+ if(field.type === "checkbox" || field.type === "radio") {
73
+ field.checked = field.value === fieldValue;
74
+ } else if(field.tagName === 'SELECT') {
75
+ // Check for Selectize
76
+ if (field.className.indexOf('selectized') != -1) {
77
+ // Field uses Selectize
78
+ field.selectize.setValue(fieldValue, true);
79
+ } else {
80
+ // Regular SELECT Field
81
+ for(let option of field.options) {
82
+ if(option.value === fieldValue) {
83
+ option.selected = true;
84
+ break;
85
+ }
86
+ }
87
+ }
88
+ } else {
89
+ field.value = fieldValue;
90
+ }
91
+ }
92
+ }
93
+ }
94
+
95
+ const clearFilterButton = document.getElementsByClassName('administrate-filterable__clear-filter')[0];
96
+ // Clear all query params on clear filter click
97
+ clearFilterButton.addEventListener('click', function(event) {
98
+ // Prevent form submission
99
+ event.preventDefault();
100
+ window.location = removeQueryParams(window.location.href);
101
+ });
102
+
103
+ function removeQueryParams(url) {
104
+ let parsedUrl = new URL(url);
105
+
106
+ return parsedUrl.origin + parsedUrl.pathname;
107
+ }
@@ -0,0 +1,92 @@
1
+ .administrate-filterable__toggle-button {
2
+ position: fixed;
3
+ z-index: 4;
4
+ top: 38px;
5
+ right: 10px;
6
+ }
7
+
8
+ .administrate-filterable__overlay {
9
+ position: fixed;
10
+ z-index: 2;
11
+ top: 0;
12
+ left: 0;
13
+ width: 100%;
14
+ height: 100%;
15
+ background: #1C1D21;
16
+ opacity: 0;
17
+ visibility: hidden;
18
+ transition: opacity .5s, visibility .5s;
19
+ }
20
+
21
+ .administrate-filterable__open .administrate-filterable__overlay {
22
+ opacity: .6;
23
+ visibility: visible;
24
+ }
25
+
26
+ .administrate-filterable__container {
27
+ position: fixed;
28
+ z-index: 3;
29
+ top: 0;
30
+ right: 0;
31
+ height: 100%;
32
+ width: 90%;
33
+ max-width: 460px;
34
+ padding: 2em 3.5em;
35
+ background: #F3F3F3;
36
+ overflow: auto;
37
+ transform: translateZ(0);
38
+ transform: translateX(100%);
39
+ transition: transform .5s cubic-bezier(.07,.23,.34,1);
40
+ }
41
+
42
+ .administrate-filterable__open .administrate-filterable__container {
43
+ transform: translateX(0);
44
+ }
45
+
46
+ .administrate-filterable__container h2 {
47
+ font-size: 15px;
48
+ font-weight: bold;
49
+ text-transform: uppercase;
50
+ color: #AAAAAA;
51
+ margin: 1.5em 0;
52
+ }
53
+
54
+ @keyframes slide-in {
55
+ 0% {
56
+ opacity: 0;
57
+ transform: translateX(80px);
58
+ }
59
+
60
+ 100% {
61
+ opacity: 1;
62
+ transform: translateX(0);
63
+ }
64
+ }
65
+
66
+ .administrate-filterable__form .field-unit {
67
+ display: block;
68
+ }
69
+
70
+ .administrate-filterable__form .field-unit__label {
71
+ width: 100%;
72
+ margin-left: 0;
73
+ text-align: left;
74
+ margin-bottom: 5px;
75
+ }
76
+
77
+ .administrate-filterable__form .field-unit__field {
78
+ margin-left: 0;
79
+ }
80
+
81
+ .administrate-filterable__form .form-actions {
82
+ margin-left: 0;
83
+ text-align: center;
84
+ }
85
+
86
+ .administrate-filterable__form .administrate-filterable__clear-filter {
87
+ background: #888888;
88
+ }
89
+
90
+ .administrate-filterable__form .administrate-filterable__clear-filter:hover {
91
+ background: #666666;
92
+ }
@@ -0,0 +1,32 @@
1
+ <% if @administrate_filterable_attributes.present? %>
2
+ <% resource_title = display_resource_name(page.resource_name) %>
3
+
4
+ <% # TODO: Improve the toggle button user experience (e.g. add open/close animation, add dynamic open/close title, etc) %>
5
+ <a href="#administrate-filterable" class="administrate-filterable__toggle-button" title="Filter <%= resource_title %>">
6
+ <svg style="width: 44px; height: 44px;" width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
7
+ <path d="M21 6H19M21 12H16M21 18H16M7 20V13.5612C7 13.3532 7 13.2492 6.97958 13.1497C6.96147 13.0615 6.93151 12.9761 6.89052 12.8958C6.84431 12.8054 6.77934 12.7242 6.64939 12.5617L3.35061 8.43826C3.22066 8.27583 3.15569 8.19461 3.10948 8.10417C3.06849 8.02393 3.03853 7.93852 3.02042 7.85026C3 7.75078 3 7.64677 3 7.43875V5.6C3 5.03995 3 4.75992 3.10899 4.54601C3.20487 4.35785 3.35785 4.20487 3.54601 4.10899C3.75992 4 4.03995 4 4.6 4H13.4C13.9601 4 14.2401 4 14.454 4.10899C14.6422 4.20487 14.7951 4.35785 14.891 4.54601C15 4.75992 15 5.03995 15 5.6V7.43875C15 7.64677 15 7.75078 14.9796 7.85026C14.9615 7.93852 14.9315 8.02393 14.8905 8.10417C14.8443 8.19461 14.7793 8.27583 14.6494 8.43826L11.3506 12.5617C11.2207 12.7242 11.1557 12.8054 11.1095 12.8958C11.0685 12.9761 11.0385 13.0615 11.0204 13.1497C11 13.2492 11 13.3532 11 13.5612V17L7 20Z" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
8
+ </svg>
9
+ </a>
10
+
11
+ <div class="administrate-filterable__container" id="administrate-filterable">
12
+ <header>
13
+ <h2>Filter <%= resource_title %></h2>
14
+ </header>
15
+
16
+ <%= form_with(model: [:admin, @administrate_filterable_resource], method: :get, html: { class: "form administrate-filterable__form" }) do |f| %>
17
+ <% @administrate_filterable_attributes.each do |attribute| -%>
18
+ <% # TODO: Add capability to customize the filter behavior (e.g. search by exact match, search by partial match, etc just like in the ActiveAdmin filter) %>
19
+ <div class="field-unit field-unit--<%= attribute.html_class %> administrate-filterable__field">
20
+ <%= render_field attribute, f: f %>
21
+ </div>
22
+ <% end -%>
23
+
24
+ <div class="form-actions">
25
+ <%= f.button 'Clear Filter', class: 'administrate-filterable__clear-filter' %>
26
+ <%= f.submit 'Apply Filter', class: 'administrate-filterable__apply-filter' %>
27
+ </div>
28
+ <% end %>
29
+ </div>
30
+
31
+ <div class="administrate-filterable__overlay"></div>
32
+ <% end %>
@@ -0,0 +1,19 @@
1
+ <% content_for(:title) do %>
2
+ <%= display_resource_name(page.resource_name) %>
3
+ <% end %>
4
+
5
+ <header class="main-content__header">
6
+ <h1 class="main-content__page-title" id="page-title">
7
+ <%= content_for(:title) %>
8
+ </h1>
9
+
10
+ <% if show_search_bar %>
11
+ <%= render(
12
+ "search",
13
+ search_term: search_term,
14
+ resource_name: display_resource_name(page.resource_name)
15
+ ) %>
16
+ <% end %>
17
+
18
+ <%= render('index_filter', page: page) %>
19
+ </header>
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "administrate_filterable"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,39 @@
1
+ module AdministrateFilterable
2
+ module Filterer
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ filterable
7
+ end
8
+
9
+ class_methods do
10
+ def filterable
11
+ # TODO: Figure out a better way to implement the filter functionality
12
+ # This is a hack to implement the filter functionality by overriding the scoped_resource method
13
+ # It would be better to implement this as a separate controller action, but I don't have time to explore that right now
14
+ define_method(:scoped_resource) do
15
+ # TODO: Figure out a better way to pass the filter data to the form
16
+ # This is a hack to get the filter resource and attributes to show up in the form, but it's not ideal
17
+ # So I tried to make the variable name as unique as possible to avoid collisions
18
+ @administrate_filterable_resource = resource_name.to_s.titleize.constantize.new
19
+ @administrate_filterable_attributes = FiltererService.filter_attributes(dashboard, @administrate_filterable_resource)
20
+
21
+ data = resource_class.all
22
+
23
+ filter_params = params[resource_name]
24
+ return data if filter_params.blank?
25
+
26
+ filter_params.each do |key, value|
27
+ next unless data.column_names.include?(key.to_s) && value.present?
28
+
29
+ # TODO: Add support for relational filter (e.g. filter by `belongs_to` association, etc)
30
+ sanitized_query = ActiveRecord::Base.send(:sanitize_sql_array, ["#{key} LIKE ?", "%#{value}%"])
31
+ data = data.where(sanitized_query)
32
+ end
33
+
34
+ data
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,30 @@
1
+ require 'csv'
2
+
3
+ module AdministrateFilterable
4
+ class FiltererService
5
+ def self.filter_attributes(dashboard, resource)
6
+ new(dashboard, resource).filter_attributes
7
+ end
8
+
9
+ def initialize(dashboard, resource)
10
+ @dashboard = dashboard
11
+ @resource = resource
12
+ end
13
+
14
+ def filter_attributes
15
+ form = Administrate::Page::Form.new(@dashboard, @resource)
16
+
17
+ attributes.map do |attribute|
18
+ form.send :attribute_field, @dashboard, @resource, attribute, :form
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def attributes
25
+ return @dashboard.class::FILTER_ATTRIBUTES if @dashboard.class.const_defined?("FILTER_ATTRIBUTES")
26
+
27
+ @dashboard.class::COLLECTION_ATTRIBUTES
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,3 @@
1
+ module AdministrateFilterable
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,11 @@
1
+ require "administrate_filterable/version"
2
+ require "administrate_filterable/filterer_service"
3
+ require "administrate_filterable/filterer"
4
+ require "administrate/engine"
5
+
6
+ module AdministrateFilterable
7
+ class Engine < ::Rails::Engine
8
+ Administrate::Engine.add_javascript "administrate_filterable/application"
9
+ Administrate::Engine.add_stylesheet "administrate_filterable/application"
10
+ end
11
+ end
metadata ADDED
@@ -0,0 +1,176 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: administrate_filterable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Irvan Fauziansyah
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2023-11-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.16'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: byebug
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: administrate
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 0.17.0
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 0.17.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: rails
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '4.2'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '4.2'
97
+ - !ruby/object:Gem::Dependency
98
+ name: actionview
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: 5.2.2.1
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: 5.2.2.1
111
+ - !ruby/object:Gem::Dependency
112
+ name: railties
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: 5.2.2.1
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: 5.2.2.1
125
+ description: Simple plugin to add custom filter functionality to your Administrate
126
+ index page.
127
+ email:
128
+ - ervhan@gmail.com
129
+ executables: []
130
+ extensions: []
131
+ extra_rdoc_files: []
132
+ files:
133
+ - ".github/workflows/main.yml"
134
+ - ".gitignore"
135
+ - ".rspec"
136
+ - CHANGELOG
137
+ - Gemfile
138
+ - Gemfile.lock
139
+ - LICENSE
140
+ - README.md
141
+ - Rakefile
142
+ - administrate_filterable.gemspec
143
+ - app/assets/javascripts/administrate_filterable/application.js
144
+ - app/assets/stylesheets/administrate_filterable/application.css
145
+ - app/views/admin/application/_index_filter.html.erb
146
+ - app/views/admin/application/_index_header.html.erb
147
+ - bin/console
148
+ - bin/setup
149
+ - lib/administrate_filterable.rb
150
+ - lib/administrate_filterable/filterer.rb
151
+ - lib/administrate_filterable/filterer_service.rb
152
+ - lib/administrate_filterable/version.rb
153
+ homepage: https://github.com/IrvanFza/administrate_filterable
154
+ licenses:
155
+ - MIT
156
+ metadata: {}
157
+ post_install_message:
158
+ rdoc_options: []
159
+ require_paths:
160
+ - lib
161
+ required_ruby_version: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ required_rubygems_version: !ruby/object:Gem::Requirement
167
+ requirements:
168
+ - - ">="
169
+ - !ruby/object:Gem::Version
170
+ version: '0'
171
+ requirements: []
172
+ rubygems_version: 3.4.22
173
+ signing_key:
174
+ specification_version: 4
175
+ summary: Administrate Custom Filter
176
+ test_files: []