active_admin_scoped_collection_actions 2.0.0 → 2.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 +4 -4
- data/CHANGELOG.md +18 -2
- data/README.md +230 -120
- data/config/locales/de.yml +3 -0
- data/config/locales/en.yml +4 -0
- data/config/locales/es.yml +3 -0
- data/config/locales/sv.yml +3 -0
- data/lib/active_admin_scoped_collection_actions/dsl.rb +19 -0
- data/lib/active_admin_scoped_collection_actions/resource_extension.rb +12 -0
- data/lib/active_admin_scoped_collection_actions/version.rb +1 -1
- data/vendor/assets/javascripts/active_admin_scoped_collection_actions.js +40 -4
- data/vendor/assets/javascripts/lib/dialog_confirm_submit.js +52 -0
- data/vendor/assets/javascripts/lib/dialog_mass_fields_update.js +59 -7
- data/vendor/assets/javascripts/lib/dialog_records_summary.js +47 -0
- data/vendor/assets/stylesheets/active_admin_scoped_collection_actions.scss +39 -0
- metadata +4 -11
- data/.github/workflows/ci.yml +0 -87
- data/.gitignore +0 -15
- data/CODE_OF_CONDUCT.md +0 -13
- data/Gemfile +0 -21
- data/Rakefile +0 -7
- data/active_admin_scoped_collection_actions.gemspec +0 -23
- data/screenshots/pupup.png +0 -0
- data/screenshots/sidebar.png +0 -0
- data/tasks/test.rake +0 -14
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ad87e54407e268139e8d45334645748dd0a02a22fdecd73bb6ea07bbf8f4ebe5
|
|
4
|
+
data.tar.gz: a4f1a7c3d13f6b0d56f33b5988caf0add3638e3b65d0d49d00b4038aea720d40
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b1177f3b81e88e75871e76e08089f33ad6626b3e161963c12e59f64f83852add74271443a513a388e97e1648c8e5446040bd0a687863c4f4d398f636f7a4b377
|
|
7
|
+
data.tar.gz: 9f7eb669df1c427416300a75127a786b151a46f80f65bb40e8979ada2b666c9c80cfe045336f2099b09d284367723f2d1b02874be36c68de22ddd39e7fb43e92
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
## ActiveAdmin Scoped Collection Actions 2.1.0 (October 02, 2026) ##
|
|
2
|
+
|
|
3
|
+
* New action option `:confirm_submit` - turns the fields dialog into two steps, the second one replaces the
|
|
4
|
+
fields with amount of affected records and the changes to be applied
|
|
5
|
+
* New action option `:confirm_summary` - tells the user how many records the action affects
|
|
6
|
+
* Form field can be defined as `{type: 'text', class: 'my-widget'}` to render input with own class
|
|
7
|
+
* **The required Ruby version is now 3.3** — Ruby 3.1 and 3.2 are EOL; stay on 2.0.0 for those
|
|
8
|
+
* Tested against Ruby 3.3, 3.4 and 4.0 with Rails 8.0 and 8.1; Rails 7.1/7.2 left the CI matrix but are not excluded by any constraint
|
|
9
|
+
* The packaged gem ships only `lib`, `vendor`, `config` and the docs — no spec suite, no screenshots
|
|
10
|
+
|
|
11
|
+
## ActiveAdmin Scoped Collection Actions 2.0.0 (April 07, 2026) ##
|
|
12
|
+
|
|
13
|
+
* compatibility with ActiveAdmin 3.5, including its jQuery UI dialog changes
|
|
14
|
+
* fixed the Sprockets manifest error on Rails 8.0+
|
|
15
|
+
* New action option `:if` to conditionally hide a collection action (@qoqa #48)
|
|
16
|
+
* `:confirm` accepts a proc (@qoqa #47)
|
|
17
|
+
* dropped EOL Ruby and Rails versions
|
|
18
|
+
|
|
1
19
|
## ActiveAdmin Scoped Collection Actions 1.0.1 (March 02, 2024) ##
|
|
2
20
|
|
|
3
21
|
* compatibility with ActiveAdmin 3.x (@oskarpearson #43)
|
|
@@ -10,12 +28,10 @@
|
|
|
10
28
|
|
|
11
29
|
* Swedish locale (@buren #27)
|
|
12
30
|
|
|
13
|
-
|
|
14
31
|
## ActiveAdmin Scoped Collection Actions 0.3.3 (July 17, 2017) ##
|
|
15
32
|
|
|
16
33
|
* Fixed compatibility with Rails 5.1
|
|
17
34
|
|
|
18
|
-
|
|
19
35
|
## ActiveAdmin Scoped Collection Actions 0.3.2 (July 17, 2017) ##
|
|
20
36
|
|
|
21
37
|
* Added I18n support.
|
data/README.md
CHANGED
|
@@ -1,20 +1,40 @@
|
|
|
1
1
|
[](https://github.com/activeadmin-plugins/active_admin_scoped_collection_actions/actions/workflows/ci.yml)
|
|
2
2
|

|
|
3
3
|
|
|
4
|
-
|
|
5
4
|
# ActiveAdmin Scoped Collection Actions
|
|
5
|
+
|
|
6
6
|
Plugin for ActiveAdmin. Provides batch Update and Delete for scoped_collection (Filters + Scope) across all pages.
|
|
7
7
|
|
|
8
|
-

|
|
9
9
|
|
|
10
|
-

|
|
11
11
|
|
|
12
|
+
## Contents
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
- [Description](#description)
|
|
15
|
+
- [Installation](#installation)
|
|
16
|
+
- [Usage](#usage)
|
|
17
|
+
- [Update action](#update-action)
|
|
18
|
+
- [Custom actions](#custom-actions)
|
|
19
|
+
- [Details and settings](#details-and-settings)
|
|
20
|
+
- [Why don't I see the sidebar with collection actions?](#why-dont-i-see-the-sidebar-with-collection-actions)
|
|
21
|
+
- [Can I use my own handler for the update and delete actions?](#can-i-use-my-own-handler-for-the-update-and-delete-actions)
|
|
22
|
+
- [How can I rename a button?](#how-can-i-rename-a-button)
|
|
23
|
+
- [How can I change the modal dialog title?](#how-can-i-change-the-modal-dialog-title)
|
|
24
|
+
- [How can I tell the user how many records are affected?](#how-can-i-tell-the-user-how-many-records-are-affected)
|
|
25
|
+
- [How can I ask for an extra confirmation before the form is submitted?](#how-can-i-ask-for-an-extra-confirmation-before-the-form-is-submitted)
|
|
26
|
+
- [Can I replace the pop-up form with my own?](#can-i-replace-the-pop-up-form-with-my-own)
|
|
27
|
+
- [How do I notify the user about success and errors?](#how-do-i-notify-the-user-about-success-and-errors)
|
|
28
|
+
- [Can I perform an action only on selected items?](#can-i-perform-an-action-only-on-selected-items)
|
|
29
|
+
- [License](#license)
|
|
14
30
|
|
|
15
|
-
|
|
31
|
+
## Description
|
|
16
32
|
|
|
17
|
-
|
|
33
|
+
This gem gives you the ability to perform various batch actions on any filtered (or scoped) resource. An action
|
|
34
|
+
applies to all records across all pages. It is similar to an ActiveAdmin batch action, but affects all filtered
|
|
35
|
+
records. This is useful when you want to delete or update a lot of records in one click.
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
18
38
|
|
|
19
39
|
Add this line to your application's Gemfile:
|
|
20
40
|
|
|
@@ -27,23 +47,23 @@ gem 'active_admin_scoped_collection_actions', github: 'activeadmin-plugins/activ
|
|
|
27
47
|
|
|
28
48
|
And then execute:
|
|
29
49
|
|
|
30
|
-
```
|
|
31
|
-
|
|
50
|
+
```sh
|
|
51
|
+
bundle
|
|
32
52
|
```
|
|
33
53
|
|
|
34
|
-
Add the following line at the end of
|
|
54
|
+
Add the following line at the end of `app/assets/javascript/active_admin.js`:
|
|
35
55
|
|
|
36
56
|
```javascript
|
|
37
57
|
//= require active_admin_scoped_collection_actions
|
|
38
58
|
```
|
|
39
59
|
|
|
40
|
-
Also include CSS in
|
|
60
|
+
Also include the CSS in `app/assets/stylesheets/active_admin.css.scss`:
|
|
41
61
|
|
|
42
|
-
```
|
|
62
|
+
```scss
|
|
43
63
|
@import "active_admin_scoped_collection_actions";
|
|
44
64
|
```
|
|
45
65
|
|
|
46
|
-
|
|
66
|
+
## Usage
|
|
47
67
|
|
|
48
68
|
Usually you need two standard actions: Delete and Update.
|
|
49
69
|
|
|
@@ -67,37 +87,53 @@ ActiveAdmin.register Post do
|
|
|
67
87
|
end
|
|
68
88
|
```
|
|
69
89
|
|
|
70
|
-
**Important
|
|
90
|
+
> **Important**
|
|
91
|
+
> Visit the Posts page with your browser and you will see no changes. Now, perform any filter with the Filters
|
|
92
|
+
> sidebar. Only after you filter will you see a delete button. It will be in the sidebar under Filters.
|
|
71
93
|
|
|
72
94
|
### Update action
|
|
73
95
|
|
|
74
|
-
Update is second standard action and is more complex. It has
|
|
96
|
+
Update is the second standard action and is more complex. It has a `form` hash wrapped in a Proc:
|
|
75
97
|
|
|
76
98
|
```ruby
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
end
|
|
99
|
+
scoped_collection_action :scoped_collection_update, form: -> do
|
|
100
|
+
{ name: 'text',
|
|
101
|
+
diagonal: 'text',
|
|
102
|
+
manufactured_at: 'datepicker',
|
|
103
|
+
vendor_id: Vendor.all.map { |region| [region.name, region.id] },
|
|
104
|
+
has_3g: [['Yes', 't'], ['No', 'f']] }
|
|
105
|
+
end
|
|
85
106
|
```
|
|
86
107
|
|
|
87
|
-
In this example Phone model has fields:
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
108
|
+
In this example the Phone model has fields:
|
|
109
|
+
|
|
110
|
+
- `name` — varchar string
|
|
111
|
+
- `diagonal` — integer (or float)
|
|
112
|
+
- `manufactured_at` — datetime
|
|
113
|
+
- `vendor_id` — association `belongs_to :vendor, class_name: 'Vendor', foreign_key: :vendor_id`
|
|
114
|
+
- `has_3g` — boolean
|
|
93
115
|
|
|
94
|
-
|
|
116
|
+
The `form` parameter is a Proc which returns a Hash. It defines which fields you want to be able to update. Hash
|
|
117
|
+
keys are column names in the database, hash values are types of HTML inputs. Supported values are `text`,
|
|
118
|
+
`checkbox`, `datepicker`, and an Array of options for a selectbox. If you want something more complex, you can
|
|
119
|
+
build your own forms.
|
|
95
120
|
|
|
96
|
-
|
|
121
|
+
A field value can also be a Hash, `{type: 'text', class: 'my-widget'}`. Such a field is rendered as a plain input
|
|
122
|
+
with your own class on it, so you can turn it into any widget (datetime picker, autocomplete, etc.) with your own
|
|
123
|
+
JavaScript. The dialog triggers the `mass_update_modal_dialog:after_open` event on `body` with the form as an
|
|
124
|
+
argument — this is the place to initialize your widgets:
|
|
97
125
|
|
|
98
|
-
|
|
126
|
+
```javascript
|
|
127
|
+
$(document).on('mass_update_modal_dialog:after_open', function (event, form) {
|
|
128
|
+
$(form).find('input.my-widget').myWidget();
|
|
129
|
+
});
|
|
130
|
+
```
|
|
99
131
|
|
|
100
|
-
|
|
132
|
+
## Custom actions
|
|
133
|
+
|
|
134
|
+
Example: we have a Phone resource with a `manufactured_at` column. We need an action which will erase this date.
|
|
135
|
+
|
|
136
|
+
In the ActiveAdmin resource:
|
|
101
137
|
|
|
102
138
|
```ruby
|
|
103
139
|
ActiveAdmin.register Phone do
|
|
@@ -113,13 +149,12 @@ ActiveAdmin.register Phone do
|
|
|
113
149
|
end
|
|
114
150
|
```
|
|
115
151
|
|
|
116
|
-
This simple code will create a new
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
# Details and Settings
|
|
152
|
+
This simple code will create a new "Erase date" button in the sidebar. After clicking it, the user will see the
|
|
153
|
+
confirmation message "Are you sure?". After confirming, all filtered records will be updated.
|
|
120
154
|
|
|
155
|
+
## Details and settings
|
|
121
156
|
|
|
122
|
-
### Why
|
|
157
|
+
### Why don't I see the sidebar with collection actions?
|
|
123
158
|
|
|
124
159
|
Sidebar visibility by default depends on several things.
|
|
125
160
|
|
|
@@ -129,19 +164,18 @@ First you must set:
|
|
|
129
164
|
config.batch_actions = true
|
|
130
165
|
```
|
|
131
166
|
|
|
132
|
-
|
|
167
|
+
Internally this gem uses `batch_actions`, so without them collection actions will not work.
|
|
133
168
|
|
|
134
169
|
```ruby
|
|
135
170
|
scoped_collection_action :something_here
|
|
136
171
|
```
|
|
137
172
|
|
|
138
|
-
|
|
173
|
+
Your resource should have some collection actions. If it does not have any, the sidebar will not appear.
|
|
139
174
|
|
|
140
|
-
By default we
|
|
175
|
+
By default we do not allow performing actions on **all** the records — this protects you from accidental
|
|
176
|
+
deletion. The sidebar with buttons appears only after you filter or scope the resource records.
|
|
141
177
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
And lastly you can manage sidebar visibility by resource config:
|
|
178
|
+
And lastly, you can manage sidebar visibility through the resource config:
|
|
145
179
|
|
|
146
180
|
```ruby
|
|
147
181
|
# Always
|
|
@@ -151,91 +185,163 @@ config.scoped_collection_actions_if = -> { params[:scope] }
|
|
|
151
185
|
# etc.
|
|
152
186
|
```
|
|
153
187
|
|
|
154
|
-
You can also manage visibility of each action individually:
|
|
188
|
+
You can also manage the visibility of each action individually:
|
|
155
189
|
|
|
156
190
|
```ruby
|
|
157
191
|
scoped_collection_action :scoped_collection_destroy, if: proc { can? :destroy, Blog }
|
|
158
192
|
```
|
|
159
193
|
|
|
160
|
-
### Can I use my handler
|
|
194
|
+
### Can I use my own handler for the update and delete actions?
|
|
161
195
|
|
|
162
|
-
You can pass block to default
|
|
163
|
-
|
|
196
|
+
You can pass a block to the default update and delete actions, and do a custom redirect after it. Use `head`
|
|
197
|
+
with a `location:` instead of `redirect_to`.
|
|
164
198
|
|
|
165
|
-
This example renders form which allows
|
|
199
|
+
This example renders a form which allows changing the `name` field, and after that redirects to the dashboard
|
|
200
|
+
page:
|
|
166
201
|
|
|
167
202
|
```ruby
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
203
|
+
scoped_collection_action :scoped_collection_update,
|
|
204
|
+
form: -> {
|
|
205
|
+
{ name: 'text' }
|
|
206
|
+
} do
|
|
207
|
+
scoped_collection_records.update_all(name: params[:changes][:name])
|
|
208
|
+
flash[:notice] = 'Name successfully changed.'
|
|
209
|
+
head :no_content, location: admin_dashboard_path
|
|
210
|
+
end
|
|
176
211
|
```
|
|
177
212
|
|
|
213
|
+
### How can I rename a button?
|
|
178
214
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
Every scoped_collection_action has option `:title`.
|
|
215
|
+
Every `scoped_collection_action` has the `:title` option.
|
|
182
216
|
|
|
183
217
|
Example:
|
|
184
218
|
|
|
185
219
|
```ruby
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
220
|
+
scoped_collection_action :erase_date, title: 'Nullify' do
|
|
221
|
+
scoped_collection_records.update_all(manufactured_at: nil)
|
|
222
|
+
end
|
|
189
223
|
```
|
|
190
224
|
|
|
225
|
+
### How can I change the modal dialog title?
|
|
226
|
+
|
|
227
|
+
Similar to the button title — use the `:confirm` option:
|
|
228
|
+
|
|
229
|
+
```ruby
|
|
230
|
+
scoped_collection_action :scoped_collection_destroy, confirm: 'Delete all phones?'
|
|
231
|
+
```
|
|
191
232
|
|
|
192
|
-
### How can I
|
|
233
|
+
### How can I tell the user how many records are affected?
|
|
193
234
|
|
|
194
|
-
|
|
235
|
+
The `:confirm_summary` option adds a sentence with the number of affected records to the dialog:
|
|
195
236
|
|
|
196
237
|
```ruby
|
|
197
|
-
|
|
238
|
+
scoped_collection_action :scoped_collection_destroy, confirm_summary: true
|
|
198
239
|
```
|
|
199
240
|
|
|
241
|
+
```text
|
|
242
|
+
Delete all?
|
|
243
|
+
|
|
244
|
+
You are going to delete 100500 record(s).
|
|
245
|
+
|
|
246
|
+
[ OK ] [ Cancel ]
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
`true` uses the default text — the `confirm_destroy_summary` locale key for `:scoped_collection_destroy` and
|
|
250
|
+
`confirm_action_summary` for any other action. Pass a String or a Proc to use your own text. `{count}` in the
|
|
251
|
+
text is replaced with the number.
|
|
252
|
+
|
|
253
|
+
The amount is requested from the server with the same filters, scope and checked records the action itself will
|
|
254
|
+
use, so it costs one extra COUNT query. Until the answer arrives the message is not displayed at all — there is
|
|
255
|
+
a spinner in its place and OK is disabled, so nothing can be confirmed before the user sees what they confirm.
|
|
256
|
+
|
|
257
|
+
When that request happens depends on which option is set:
|
|
258
|
+
|
|
259
|
+
- neither `:confirm_summary` nor `:confirm_submit` — nothing is counted and no request is made;
|
|
260
|
+
- `:confirm_summary` — once, when the dialog is opened;
|
|
261
|
+
- `:confirm_submit` — once, when OK is pressed and the second step appears, and not at all when the user checked
|
|
262
|
+
no fields, because then there is nothing to confirm and the action runs straight away.
|
|
200
263
|
|
|
201
|
-
|
|
264
|
+
Setting both does **not** make two requests. `:confirm_submit` takes over, and `:confirm_summary` only supplies
|
|
265
|
+
the wording for the sentence on its second step.
|
|
202
266
|
|
|
203
|
-
|
|
267
|
+
Both options in one flow — an update action with `confirm_submit:` and the summary it shows, from the sidebar
|
|
268
|
+
button to the result:
|
|
204
269
|
|
|
270
|
+

|
|
271
|
+
|
|
272
|
+
### How can I ask for an extra confirmation before the form is submitted?
|
|
273
|
+
|
|
274
|
+
An action without a form is confirmed by the modal dialog itself — it has no fields and the user just presses OK.
|
|
275
|
+
An action with a form applies changes immediately after OK is pressed. Use the `:confirm_submit` option to turn
|
|
276
|
+
such an action into two steps, where the second one replaces the fields with a summary of what is going to
|
|
277
|
+
happen:
|
|
205
278
|
|
|
206
279
|
```ruby
|
|
207
|
-
|
|
280
|
+
scoped_collection_action :scoped_collection_update,
|
|
281
|
+
confirm_submit: true,
|
|
282
|
+
form: -> { { body: 'text' } }
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
```text
|
|
286
|
+
Are you sure?
|
|
287
|
+
|
|
288
|
+
You are going to update 100500 record(s) with:
|
|
289
|
+
Body: Text here...
|
|
290
|
+
Author_id: Jane
|
|
291
|
+
|
|
292
|
+
[ OK ] [ Back ]
|
|
208
293
|
```
|
|
209
294
|
|
|
210
|
-
|
|
295
|
+
"Back" returns to the form with everything the user filled in still there. Only the fields the user checked are
|
|
296
|
+
listed, and when nothing is checked there is nothing to confirm — the action is performed right away. The text of
|
|
297
|
+
the sentence is the `confirm_submit_summary` locale key, or the `:confirm_summary` option when you set it.
|
|
298
|
+
|
|
299
|
+
`true` shows the default message ("Are you sure?"). Pass a String or a Proc to use your own:
|
|
300
|
+
|
|
301
|
+
```ruby
|
|
302
|
+
scoped_collection_action :scoped_collection_update,
|
|
303
|
+
confirm_submit: 'Update all filtered phones?',
|
|
304
|
+
form: -> { { name: 'text' } }
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Can I replace the pop-up form with my own?
|
|
308
|
+
|
|
309
|
+
Yes, but you must also take care of the mandatory parameters passed to the server.
|
|
310
|
+
|
|
311
|
+
```ruby
|
|
312
|
+
scoped_collection_action :my_pop_action, class: 'my_popup'
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Now in the HTML page you have a button:
|
|
211
316
|
|
|
212
317
|
```html
|
|
213
|
-
|
|
318
|
+
<button class="my_popup" data="{"auth_token":"2a+KLu5u9McQENspCiep0DGZI6D09fCVXAN9inrwRG0=","batch_action":"my_pop_action","confirm":"Are you sure?"}">My pop action</button>
|
|
214
319
|
```
|
|
215
320
|
|
|
216
|
-
Without handler, clicking
|
|
321
|
+
Without a handler, clicking the button does nothing.
|
|
217
322
|
|
|
218
323
|
You can render the form in any way you want:
|
|
219
|
-
- It can be some popup(Fancybox, Simplemodal, etc.), or some inline collapsible form.
|
|
220
|
-
- It can even be a separate full-page.
|
|
221
|
-
|
|
222
|
-
One thing is important - how you will send data to server. Generally it should be:
|
|
223
324
|
|
|
224
|
-
|
|
325
|
+
- it can be a popup (Fancybox, Simplemodal, etc.), or an inline collapsible form;
|
|
326
|
+
- it can even be a separate full page.
|
|
225
327
|
|
|
226
|
-
|
|
328
|
+
One thing is important — how you send the data to the server. It should be:
|
|
227
329
|
|
|
228
|
-
|
|
330
|
+
| | |
|
|
331
|
+
| --- | --- |
|
|
332
|
+
| Method | `POST` |
|
|
333
|
+
| URL | `/admin/collection_path/batch_action` |
|
|
334
|
+
| Query string | identical to the current page |
|
|
229
335
|
|
|
230
|
-
The easiest way to
|
|
336
|
+
The easiest way to build it is:
|
|
231
337
|
|
|
232
338
|
```javascript
|
|
233
339
|
url = window.location.pathname + '/batch_action' + window.location.search
|
|
234
340
|
```
|
|
235
341
|
|
|
236
|
-
And
|
|
342
|
+
And the request body params should look like:
|
|
237
343
|
|
|
238
|
-
```
|
|
344
|
+
```text
|
|
239
345
|
changes[manufactured_at] = "2015-07-21 18:11"
|
|
240
346
|
changes[diagonal] = "7"
|
|
241
347
|
changes[some_filed_name]='new value'
|
|
@@ -243,69 +349,73 @@ authenticity_token = "2a+KLu5u9McQENspCiep0DGZI6D09fCVXAN9inrwRG0="
|
|
|
243
349
|
batch_action = "my_pop_action"
|
|
244
350
|
```
|
|
245
351
|
|
|
246
|
-
|
|
247
|
-
|
|
352
|
+
`authenticity_token` and `batch_action` can be taken from the data attribute of the button.
|
|
248
353
|
|
|
249
354
|
Example in JavaScript:
|
|
250
355
|
|
|
251
356
|
```javascript
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
357
|
+
url = window.location.pathname + '/batch_action' + window.location.search
|
|
358
|
+
form_data = {
|
|
359
|
+
changes: { "manufactured_at": "2015-07-21 18:11", "diagonal": "7" },
|
|
360
|
+
collection_selection: [],
|
|
361
|
+
authenticity_token: "2a+KLu5u9McQENspCiep0DGZI6D09fCVXAN9inrwRG0=",
|
|
362
|
+
batch_action: "my_pop_action"
|
|
363
|
+
}
|
|
364
|
+
$.post(url, form_data).always(function () {
|
|
365
|
+
window.location.reload();
|
|
366
|
+
});
|
|
261
367
|
```
|
|
262
368
|
|
|
263
|
-
### How notify user about success and
|
|
369
|
+
### How do I notify the user about success and errors?
|
|
264
370
|
|
|
265
|
-
We recommend using Rails
|
|
371
|
+
We recommend using Rails flash messages.
|
|
266
372
|
|
|
267
|
-
Example with updating phone diagonal attribute. In this case
|
|
373
|
+
Example with updating the phone diagonal attribute. In this case the Phone model has a validation:
|
|
268
374
|
|
|
269
375
|
```ruby
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
376
|
+
class Phone < ActiveRecord::Base
|
|
377
|
+
validates :diagonal, numericality: { only_integer: true }
|
|
378
|
+
end
|
|
273
379
|
```
|
|
274
380
|
|
|
275
381
|
```ruby
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
end
|
|
281
|
-
if errors.empty?
|
|
282
|
-
flash[:notice] = 'Diagonal changed successfully'
|
|
283
|
-
else
|
|
284
|
-
flash[:error] = errors.join('. ')
|
|
285
|
-
end
|
|
286
|
-
render nothing: true, status: :no_content
|
|
382
|
+
scoped_collection_action :change_diagonal, form: { diagonal: 'text' } do
|
|
383
|
+
errors = []
|
|
384
|
+
scoped_collection_records.find_each do |record|
|
|
385
|
+
errors << record.errors.full_messages.join('. ') unless record.update(diagonal: params[:changes][:diagonal])
|
|
287
386
|
end
|
|
387
|
+
if errors.empty?
|
|
388
|
+
flash[:notice] = 'Diagonal changed successfully'
|
|
389
|
+
else
|
|
390
|
+
flash[:error] = errors.join('. ')
|
|
391
|
+
end
|
|
392
|
+
head :no_content
|
|
393
|
+
end
|
|
288
394
|
```
|
|
289
395
|
|
|
290
|
-
When you try to update diagonal with "5.6" you will see flash error:
|
|
396
|
+
When you try to update the diagonal with "5.6" you will see a flash error:
|
|
291
397
|
|
|
292
|
-
```
|
|
398
|
+
```text
|
|
293
399
|
Diagonal must be an integer.
|
|
294
400
|
```
|
|
295
401
|
|
|
296
|
-
But if you use your custom popup, you can show messages with JS.
|
|
297
|
-
|
|
402
|
+
But if you use your own custom popup, you can show messages with JS.
|
|
298
403
|
|
|
299
|
-
### Can I perform action only on selected items?
|
|
404
|
+
### Can I perform an action only on selected items?
|
|
300
405
|
|
|
301
|
-
|
|
406
|
+
The standard index page of a resource with `batch_action` enabled has a selectable column.
|
|
302
407
|
|
|
303
|
-
If you checked some items and
|
|
408
|
+
If you have checked some items and perform any collection action, the handler will take care of it. If you write
|
|
409
|
+
custom actions, you should do it like this:
|
|
304
410
|
|
|
305
411
|
```ruby
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
end
|
|
412
|
+
scoped_collection_action :do_something do
|
|
413
|
+
scoped_collection_records.find_each do |record|
|
|
414
|
+
record.update(name: 'x')
|
|
310
415
|
end
|
|
416
|
+
end
|
|
311
417
|
```
|
|
418
|
+
|
|
419
|
+
## License
|
|
420
|
+
|
|
421
|
+
Released under the [MIT License](LICENSE.txt).
|
data/config/locales/de.yml
CHANGED
|
@@ -2,6 +2,9 @@ de:
|
|
|
2
2
|
active_admin_scoped_collection_actions:
|
|
3
3
|
sidebar_msg: 'Diese Aktion betrifft alle ausgewählten Datensätze. Wenn keine ausgewählt sind, betrifft sie alle gefilterten Datensätze.'
|
|
4
4
|
confirm_action_message: 'Sind Sie sicher?'
|
|
5
|
+
confirm_submit_summary: 'Sie werden {count} Datensätze aktualisieren mit:'
|
|
6
|
+
confirm_destroy_summary: 'Sie werden {count} Datensätze löschen.'
|
|
7
|
+
confirm_action_summary: 'Sie werden diese Aktion für {count} Datensätze ausführen.'
|
|
5
8
|
actions:
|
|
6
9
|
delete: 'Löschen'
|
|
7
10
|
update: 'Aktualisieren'
|
data/config/locales/en.yml
CHANGED
|
@@ -2,6 +2,10 @@ en:
|
|
|
2
2
|
active_admin_scoped_collection_actions:
|
|
3
3
|
sidebar_msg: 'This batch operations affect selected records. Or if none is selected, it will involve all records by current filters and scopes.'
|
|
4
4
|
confirm_action_message: 'Are you sure?'
|
|
5
|
+
# {count} is replaced with amount of affected records
|
|
6
|
+
confirm_submit_summary: 'You are going to update {count} record(s) with:'
|
|
7
|
+
confirm_destroy_summary: 'You are going to delete {count} record(s).'
|
|
8
|
+
confirm_action_summary: 'You are going to perform this action on {count} record(s).'
|
|
5
9
|
actions:
|
|
6
10
|
delete: 'Delete batch'
|
|
7
11
|
update: 'Update batch'
|
data/config/locales/es.yml
CHANGED
|
@@ -2,6 +2,9 @@ es:
|
|
|
2
2
|
active_admin_scoped_collection_actions:
|
|
3
3
|
sidebar_msg: 'Estas operaciones afectan las filas seleccionadas o en caso que no halla seleccionado ninguna, afectará a todo los registros afectados por los filtros aplicados.'
|
|
4
4
|
confirm_action_message: '¿Está seguro que quiere realizar esta acción?'
|
|
5
|
+
confirm_submit_summary: 'Va a actualizar {count} registro(s) con:'
|
|
6
|
+
confirm_destroy_summary: 'Va a eliminar {count} registro(s).'
|
|
7
|
+
confirm_action_summary: 'Va a realizar esta acción sobre {count} registro(s).'
|
|
5
8
|
actions:
|
|
6
9
|
delete: 'Batch Borrado'
|
|
7
10
|
update: 'Batch Actualizado'
|
data/config/locales/sv.yml
CHANGED
|
@@ -2,6 +2,9 @@ sv:
|
|
|
2
2
|
active_admin_scoped_collection_actions:
|
|
3
3
|
sidebar_msg: 'Den här bulkoperationen påverkar markerade poster. Eller om inget är markerat så kommer det påverka alla poster i nuvarande filter och scope.'
|
|
4
4
|
confirm_action_message: 'Är du säker?'
|
|
5
|
+
confirm_submit_summary: 'Du kommer att uppdatera {count} post(er) med:'
|
|
6
|
+
confirm_destroy_summary: 'Du kommer att ta bort {count} post(er).'
|
|
7
|
+
confirm_action_summary: 'Du kommer att utföra den här åtgärden på {count} post(er).'
|
|
5
8
|
actions:
|
|
6
9
|
delete: 'Ta bort batch'
|
|
7
10
|
update: 'Uppdatera batch'
|
|
@@ -2,6 +2,7 @@ module ActiveAdminScopedCollectionActions
|
|
|
2
2
|
module DSL
|
|
3
3
|
|
|
4
4
|
def scoped_collection_action(name, options = {}, &block)
|
|
5
|
+
add_scoped_collection_records_count_action
|
|
5
6
|
if name == :scoped_collection_destroy
|
|
6
7
|
options[:title] = I18n.t('active_admin_scoped_collection_actions.actions.delete') if options[:title].nil?
|
|
7
8
|
add_scoped_collection_action_default_destroy(options, &block)
|
|
@@ -15,6 +16,24 @@ module ActiveAdminScopedCollectionActions
|
|
|
15
16
|
config.add_scoped_collection_action(name, options)
|
|
16
17
|
end
|
|
17
18
|
|
|
19
|
+
# Amount of records affected by a scoped collection action. Used by the confirmation
|
|
20
|
+
# dialog of an action with :confirm_submit option. Collection is already limited by
|
|
21
|
+
# authorization scope, so it exposes nothing the user can not see on the index page.
|
|
22
|
+
def add_scoped_collection_records_count_action
|
|
23
|
+
batch_action :scoped_collection_records_count, if: proc { false } do
|
|
24
|
+
# Count the rows this relation yields, not the rows its WHERE matches.
|
|
25
|
+
# The action walks the same relation with find_each, which honours
|
|
26
|
+
# limit and offset, so stripping them here would promise more records
|
|
27
|
+
# than it goes on to touch. Counting through a subquery keeps every
|
|
28
|
+
# clause and collapses a grouped scope -- whose bare .count answers
|
|
29
|
+
# with a Hash the dialog would render as [object Object] -- into the
|
|
30
|
+
# single number the dialog needs.
|
|
31
|
+
scope = scoped_collection_records.except(:eager_load, :select, :order)
|
|
32
|
+
count = scope.model.unscoped.from(scope.select('1'), :subquery_for_count).count
|
|
33
|
+
render json: { count: count }
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
18
37
|
def add_scoped_collection_action_default_update(options, &block)
|
|
19
38
|
batch_action :scoped_collection_update, if: proc { false } do
|
|
20
39
|
unless authorized?(:batch_edit, resource_class)
|
|
@@ -69,6 +69,18 @@ module ActiveAdminScopedCollectionActions
|
|
|
69
69
|
end
|
|
70
70
|
b_data[:confirm] = options.fetch(:confirm, I18n.t('active_admin_scoped_collection_actions.confirm_action_message'))
|
|
71
71
|
b_data[:confirm] = b_data[:confirm].call if b_data[:confirm].is_a?(Proc)
|
|
72
|
+
confirm_submit = options.fetch(:confirm_submit, false)
|
|
73
|
+
confirm_submit = confirm_submit.call if confirm_submit.is_a?(Proc)
|
|
74
|
+
if confirm_submit
|
|
75
|
+
b_data[:confirm_submit] = confirm_submit.is_a?(String) ? confirm_submit : I18n.t('active_admin_scoped_collection_actions.confirm_action_message')
|
|
76
|
+
b_data[:confirm_submit_summary] = I18n.t('active_admin_scoped_collection_actions.confirm_submit_summary')
|
|
77
|
+
end
|
|
78
|
+
confirm_summary = options.fetch(:confirm_summary, false)
|
|
79
|
+
confirm_summary = confirm_summary.call if confirm_summary.is_a?(Proc)
|
|
80
|
+
if confirm_summary
|
|
81
|
+
default_key = key.to_sym == :scoped_collection_destroy ? 'confirm_destroy_summary' : 'confirm_action_summary'
|
|
82
|
+
b_data[:confirm_summary] = confirm_summary.is_a?(String) ? confirm_summary : I18n.t("active_admin_scoped_collection_actions.#{default_key}")
|
|
83
|
+
end
|
|
72
84
|
b_options[:data] = b_data.to_json
|
|
73
85
|
button b_title, b_options
|
|
74
86
|
end
|
|
@@ -1,4 +1,26 @@
|
|
|
1
|
+
//= require ./lib/dialog_records_summary
|
|
1
2
|
//= require ./lib/dialog_mass_fields_update
|
|
3
|
+
//= require ./lib/dialog_confirm_submit
|
|
4
|
+
|
|
5
|
+
ActiveAdmin.scopedCollectionBatchActionUrl = function(){
|
|
6
|
+
return window.location.pathname + '/batch_action' + window.location.search;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
ActiveAdmin.scopedCollectionSelection = function(){
|
|
10
|
+
const selection = [];
|
|
11
|
+
$('.paginated_collection').find('input.collection_selection:checked').each((i, el) => selection.push($(el).val()));
|
|
12
|
+
return selection;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// Amount of records the action is going to be performed on. Same scope(filters,
|
|
16
|
+
// current scope and checked records) as the action itself uses.
|
|
17
|
+
ActiveAdmin.scopedCollectionRecordsCount = function(authToken){
|
|
18
|
+
return $.post(ActiveAdmin.scopedCollectionBatchActionUrl(), {
|
|
19
|
+
batch_action: 'scoped_collection_records_count',
|
|
20
|
+
authenticity_token: authToken,
|
|
21
|
+
collection_selection: ActiveAdmin.scopedCollectionSelection()
|
|
22
|
+
});
|
|
23
|
+
};
|
|
2
24
|
|
|
3
25
|
$(document).ready(() =>
|
|
4
26
|
|
|
@@ -6,16 +28,30 @@ $(document).ready(() =>
|
|
|
6
28
|
e.preventDefault();
|
|
7
29
|
const fields = JSON.parse( $(this).attr('data') );
|
|
8
30
|
|
|
31
|
+
const count = () => ActiveAdmin.scopedCollectionRecordsCount(fields['auth_token']);
|
|
32
|
+
const options = {};
|
|
33
|
+
|
|
34
|
+
if (fields['confirm_submit']) {
|
|
35
|
+
// fields dialog gets a second step with the summary of what is going to be done
|
|
36
|
+
options['confirmSubmit'] = {
|
|
37
|
+
title: fields['confirm_submit'],
|
|
38
|
+
summary: fields['confirm_summary'] || fields['confirm_submit_summary'],
|
|
39
|
+
count: count
|
|
40
|
+
};
|
|
41
|
+
} else if (fields['confirm_summary']) {
|
|
42
|
+
// dialog itself tells what is going to be done
|
|
43
|
+
options['summary'] = { text: fields['confirm_summary'], count: count };
|
|
44
|
+
}
|
|
45
|
+
|
|
9
46
|
return ActiveAdmin.dialogMassFieldsUpdate(fields['confirm'], fields['inputs'],
|
|
10
47
|
inputs=> {
|
|
11
|
-
const url =
|
|
48
|
+
const url = ActiveAdmin.scopedCollectionBatchActionUrl();
|
|
12
49
|
const form_data = {
|
|
13
50
|
changes: inputs,
|
|
14
|
-
collection_selection:
|
|
51
|
+
collection_selection: ActiveAdmin.scopedCollectionSelection(),
|
|
15
52
|
authenticity_token: fields['auth_token'],
|
|
16
53
|
batch_action: fields['batch_action']
|
|
17
54
|
};
|
|
18
|
-
$('.paginated_collection').find('input.collection_selection:checked').each((i, el) => form_data["collection_selection"].push($(el).val()));
|
|
19
55
|
|
|
20
56
|
return $.post(url, form_data).always(function(data, textStatus, jqXHR) {
|
|
21
57
|
if (jqXHR.getResponseHeader('Location')) {
|
|
@@ -24,6 +60,6 @@ $(document).ready(() =>
|
|
|
24
60
|
return window.location.reload();
|
|
25
61
|
}
|
|
26
62
|
});
|
|
27
|
-
});
|
|
63
|
+
}, options);
|
|
28
64
|
})
|
|
29
65
|
);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Second step of the fields dialog: instead of the fields it shows what is going to be
|
|
2
|
+
// changed and how many records are affected. "Back" returns to the filled form.
|
|
3
|
+
// form: dialog of ActiveAdmin.dialogMassFieldsUpdate
|
|
4
|
+
// options: {title: 'Are you sure?', summary: 'You are going to update {count} record(s) with:',
|
|
5
|
+
// count: function returning jqXHR with {count: 100500}}
|
|
6
|
+
// changes: [{label: 'Name', value: 'test'}, ...]
|
|
7
|
+
ActiveAdmin.dialogConfirmSubmit = function(form, options, changes, callback){
|
|
8
|
+
const widget = form.dialog('widget');
|
|
9
|
+
const fieldsStep = { content: form.children(), title: form.dialog('option', 'title'), buttons: form.dialog('option', 'buttons') };
|
|
10
|
+
|
|
11
|
+
const step = $('<div class="dialog_confirm_submit"></div>');
|
|
12
|
+
const summary = options['summary'] ?
|
|
13
|
+
ActiveAdmin.dialogRecordsSummary(options['summary'], options['count']).appendTo(step) : null;
|
|
14
|
+
const list = $('<ul class="dialog_confirm_submit_changes"></ul>').appendTo(step);
|
|
15
|
+
|
|
16
|
+
for (let change of Array.from(changes)) {
|
|
17
|
+
$('<li></li>')
|
|
18
|
+
.append($('<b></b>').text(change['label']))
|
|
19
|
+
.append(document.createTextNode(`: ${change['value']}`))
|
|
20
|
+
.appendTo(list);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
fieldsStep.content.hide();
|
|
24
|
+
step.appendTo(form);
|
|
25
|
+
widget.addClass('active_admin_dialog_confirm_submit');
|
|
26
|
+
|
|
27
|
+
form.dialog('option', {
|
|
28
|
+
title: options['title'] || fieldsStep.title,
|
|
29
|
+
buttons: {
|
|
30
|
+
OK() {
|
|
31
|
+
// amount of affected records is not known yet
|
|
32
|
+
if (form.data('waiting')) { return; }
|
|
33
|
+
|
|
34
|
+
return callback();
|
|
35
|
+
},
|
|
36
|
+
Back() {
|
|
37
|
+
form.data('waiting', false);
|
|
38
|
+
step.remove();
|
|
39
|
+
fieldsStep.content.show();
|
|
40
|
+
widget.removeClass('active_admin_dialog_confirm_submit');
|
|
41
|
+
return form.dialog('option', { title: fieldsStep.title, buttons: fieldsStep.buttons });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (summary) {
|
|
47
|
+
ActiveAdmin.dialogSpinnerUntil(step.children(), summary.data('countRequest'));
|
|
48
|
+
ActiveAdmin.dialogWaitFor(form, summary.data('countRequest'));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return form;
|
|
52
|
+
};
|
|
@@ -1,17 +1,50 @@
|
|
|
1
|
-
|
|
1
|
+
// Fields checked by the user with their values, as they are displayed in the dialog.
|
|
2
|
+
// @return [Array<Object>] [{label: 'Name', value: 'test'}, ...]
|
|
3
|
+
ActiveAdmin.dialogMassFieldsChanges = function(form){
|
|
4
|
+
const changes = [];
|
|
5
|
+
|
|
6
|
+
form.find('.mass_update_protect_fild_flag:checked').each(function() {
|
|
7
|
+
const flag = $(this);
|
|
8
|
+
const field = flag.nextAll('input, select, textarea').first();
|
|
9
|
+
let value;
|
|
10
|
+
|
|
11
|
+
if (field.is('select')) {
|
|
12
|
+
value = field.find('option:selected').map((i, option) => $(option).text().trim()).get().join(', ');
|
|
13
|
+
} else if (field.attr('type') === 'checkbox') {
|
|
14
|
+
value = field.is(':checked');
|
|
15
|
+
} else {
|
|
16
|
+
value = field.val();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
changes.push({ label: flag.next('label').text().trim(), value: value });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
return changes;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// options: {summary: {text: 'You are going to delete {count} record(s).', count: countFn},
|
|
26
|
+
// confirmSubmit: @see ActiveAdmin.dialogConfirmSubmit}
|
|
27
|
+
ActiveAdmin.dialogMassFieldsUpdate = function(message, inputs, callback, options){
|
|
28
|
+
options = options || {};
|
|
29
|
+
const confirmSubmit = options['confirmSubmit'];
|
|
2
30
|
let html = `<form id="dialog_confirm" title="${message}"><div style="padding-right:4px;padding-left:1px;margin-right:2px"><ul>`;
|
|
3
31
|
for (let name in inputs) {
|
|
4
32
|
var elem, opts, wrapper;
|
|
5
33
|
let type = inputs[name];
|
|
6
|
-
|
|
34
|
+
let klass = '';
|
|
35
|
+
if ($.isPlainObject(type)) {
|
|
36
|
+
// {type: 'text', class: 'my-widget'} - input rendered with your own class,
|
|
37
|
+
// so it can be turned into any widget by your own JS
|
|
38
|
+
[wrapper, klass, type] = Array.from(['input', type['class'] || '', type['type'] || 'text']);
|
|
39
|
+
} else if (/^(datepicker|checkbox|text)$/.test(type)) {
|
|
7
40
|
wrapper = 'input';
|
|
41
|
+
klass = type === 'datepicker' ? type : '';
|
|
8
42
|
} else if ($.isArray(type)) {
|
|
9
43
|
[wrapper, elem, opts, type] = Array.from(['select', 'option', type, '']);
|
|
10
44
|
} else {
|
|
11
45
|
throw new Error(`Unsupported input type: {${name}: ${type}}`);
|
|
12
46
|
}
|
|
13
47
|
|
|
14
|
-
let klass = type === 'datepicker' ? type : '';
|
|
15
48
|
html += `<li>
|
|
16
49
|
<input type='checkbox' class='mass_update_protect_fild_flag' value='Y' id="mass_update_dialog_${name}" />
|
|
17
50
|
<label for="mass_update_dialog_${name}"> ${name.charAt(0).toUpperCase() + name.slice(1)}</label>
|
|
@@ -43,9 +76,12 @@ ActiveAdmin.dialogMassFieldsUpdate = function(message, inputs, callback){
|
|
|
43
76
|
|
|
44
77
|
const form = $(html).appendTo('body');
|
|
45
78
|
|
|
79
|
+
const summary = options['summary'] ?
|
|
80
|
+
ActiveAdmin.dialogRecordsSummary(options['summary']['text'], options['summary']['count']).prependTo(form) : null;
|
|
81
|
+
|
|
46
82
|
$('body').trigger('mass_update_modal_dialog:before_open', [form]);
|
|
47
83
|
|
|
48
|
-
|
|
84
|
+
const dialog = form.dialog({
|
|
49
85
|
modal: true,
|
|
50
86
|
classes: { 'ui-dialog': 'ui-corner-all active_admin_dialog active_admin_dialog_mass_update_by_filter' },
|
|
51
87
|
dialogClass: 'active_admin_dialog active_admin_dialog_mass_update_by_filter',
|
|
@@ -61,9 +97,18 @@ ActiveAdmin.dialogMassFieldsUpdate = function(message, inputs, callback){
|
|
|
61
97
|
});
|
|
62
98
|
},
|
|
63
99
|
buttons: {
|
|
64
|
-
OK(
|
|
65
|
-
|
|
66
|
-
|
|
100
|
+
OK(){
|
|
101
|
+
// amount of affected records is not known yet
|
|
102
|
+
if (form.data('waiting')) { return; }
|
|
103
|
+
|
|
104
|
+
const submit = () => {
|
|
105
|
+
form.dialog('widget').find('.ui-dialog-buttonset').html('<span>Processing. Please wait...</span>');
|
|
106
|
+
return callback(form.serializeObject());
|
|
107
|
+
};
|
|
108
|
+
const changes = ActiveAdmin.dialogMassFieldsChanges(form);
|
|
109
|
+
// nothing is checked - there is nothing to confirm
|
|
110
|
+
if (!confirmSubmit || !changes.length) { return submit(); }
|
|
111
|
+
return ActiveAdmin.dialogConfirmSubmit(form, confirmSubmit, changes, submit);
|
|
67
112
|
},
|
|
68
113
|
Cancel() {
|
|
69
114
|
$('.mass_update_protect_fild_flag').off('change');
|
|
@@ -71,4 +116,11 @@ ActiveAdmin.dialogMassFieldsUpdate = function(message, inputs, callback){
|
|
|
71
116
|
}
|
|
72
117
|
}
|
|
73
118
|
});
|
|
119
|
+
|
|
120
|
+
if (summary) {
|
|
121
|
+
ActiveAdmin.dialogSpinnerUntil(summary, summary.data('countRequest'));
|
|
122
|
+
ActiveAdmin.dialogWaitFor(form, summary.data('countRequest'));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return dialog;
|
|
74
126
|
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Sentence about the records the action is going to be performed on,
|
|
2
|
+
// e.g. "You are going to delete 100500 record(s)."
|
|
3
|
+
// template: text where {count} is replaced with the amount
|
|
4
|
+
// countFn: function returning jqXHR with {count: 100500}
|
|
5
|
+
// @return [jQuery] paragraph, request promise is available as .data('countRequest')
|
|
6
|
+
ActiveAdmin.dialogRecordsSummary = function(template, countFn){
|
|
7
|
+
const summary = $('<p class="dialog_records_summary"></p>');
|
|
8
|
+
const showCount = count => summary.text(template.replace('{count}', count));
|
|
9
|
+
|
|
10
|
+
if (!countFn) {
|
|
11
|
+
showCount('');
|
|
12
|
+
return summary.data('countRequest', $.Deferred().resolve().promise());
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const request = countFn()
|
|
16
|
+
.done(data => showCount(data['count']))
|
|
17
|
+
.fail(() => showCount('?'));
|
|
18
|
+
|
|
19
|
+
return summary.data('countRequest', request);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Shows a spinner instead of the content until the promise is settled, so the user
|
|
23
|
+
// never sees a half of the message.
|
|
24
|
+
ActiveAdmin.dialogSpinnerUntil = function(content, promise){
|
|
25
|
+
const spinner = $('<div class="dialog_spinner"></div>').insertBefore(content.first());
|
|
26
|
+
|
|
27
|
+
content.hide();
|
|
28
|
+
|
|
29
|
+
return promise.always(function(){
|
|
30
|
+
spinner.remove();
|
|
31
|
+
content.show();
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Keeps OK of the dialog disabled while the promise is pending, so nothing can be
|
|
36
|
+
// confirmed before the user sees what they confirm.
|
|
37
|
+
ActiveAdmin.dialogWaitFor = function(dialog, promise){
|
|
38
|
+
const button = dialog.dialog('widget').find('.ui-dialog-buttonset button').first();
|
|
39
|
+
|
|
40
|
+
dialog.data('waiting', true);
|
|
41
|
+
button.prop('disabled', true).addClass('ui-state-disabled');
|
|
42
|
+
|
|
43
|
+
return promise.always(function(){
|
|
44
|
+
dialog.data('waiting', false);
|
|
45
|
+
button.prop('disabled', false).removeClass('ui-state-disabled');
|
|
46
|
+
});
|
|
47
|
+
};
|
|
@@ -23,6 +23,45 @@
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
.active_admin_dialog_mass_update_by_filter, .active_admin_dialog_confirm_submit {
|
|
27
|
+
.dialog_records_summary {
|
|
28
|
+
margin: 0 0 5px 0;
|
|
29
|
+
}
|
|
30
|
+
.dialog_spinner {
|
|
31
|
+
display: block;
|
|
32
|
+
box-sizing: border-box;
|
|
33
|
+
width: 24px;
|
|
34
|
+
height: 24px;
|
|
35
|
+
margin: 15px auto;
|
|
36
|
+
border: 3px solid currentColor;
|
|
37
|
+
border-right-color: transparent;
|
|
38
|
+
border-radius: 50%;
|
|
39
|
+
animation: dialog_spinner_rotate 0.8s linear infinite;
|
|
40
|
+
}
|
|
41
|
+
button:disabled {
|
|
42
|
+
cursor: default;
|
|
43
|
+
opacity: 0.6;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@keyframes dialog_spinner_rotate {
|
|
48
|
+
to { transform: rotate(360deg); }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.active_admin_dialog_confirm_submit {
|
|
52
|
+
.dialog_confirm_submit_changes {
|
|
53
|
+
margin: 5px 0 0 0;
|
|
54
|
+
|
|
55
|
+
li {
|
|
56
|
+
display: block;
|
|
57
|
+
margin: 3px 0 0 0;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
button {
|
|
61
|
+
cursor: pointer;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
26
65
|
.sidebar_section.panel {
|
|
27
66
|
.scoped_collection_action_button {
|
|
28
67
|
cursor: pointer;
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: active_admin_scoped_collection_actions
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Gena M.
|
|
@@ -37,15 +37,9 @@ executables: []
|
|
|
37
37
|
extensions: []
|
|
38
38
|
extra_rdoc_files: []
|
|
39
39
|
files:
|
|
40
|
-
- ".github/workflows/ci.yml"
|
|
41
|
-
- ".gitignore"
|
|
42
40
|
- CHANGELOG.md
|
|
43
|
-
- CODE_OF_CONDUCT.md
|
|
44
|
-
- Gemfile
|
|
45
41
|
- LICENSE.txt
|
|
46
42
|
- README.md
|
|
47
|
-
- Rakefile
|
|
48
|
-
- active_admin_scoped_collection_actions.gemspec
|
|
49
43
|
- config/locales/de.yml
|
|
50
44
|
- config/locales/en.yml
|
|
51
45
|
- config/locales/es.yml
|
|
@@ -57,11 +51,10 @@ files:
|
|
|
57
51
|
- lib/active_admin_scoped_collection_actions/engine.rb
|
|
58
52
|
- lib/active_admin_scoped_collection_actions/resource_extension.rb
|
|
59
53
|
- lib/active_admin_scoped_collection_actions/version.rb
|
|
60
|
-
- screenshots/pupup.png
|
|
61
|
-
- screenshots/sidebar.png
|
|
62
|
-
- tasks/test.rake
|
|
63
54
|
- vendor/assets/javascripts/active_admin_scoped_collection_actions.js
|
|
55
|
+
- vendor/assets/javascripts/lib/dialog_confirm_submit.js
|
|
64
56
|
- vendor/assets/javascripts/lib/dialog_mass_fields_update.js
|
|
57
|
+
- vendor/assets/javascripts/lib/dialog_records_summary.js
|
|
65
58
|
- vendor/assets/stylesheets/active_admin_scoped_collection_actions.scss
|
|
66
59
|
homepage: https://github.com/activeadmin-plugins/active_admin_scoped_collection_actions
|
|
67
60
|
licenses:
|
|
@@ -74,7 +67,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
74
67
|
requirements:
|
|
75
68
|
- - ">="
|
|
76
69
|
- !ruby/object:Gem::Version
|
|
77
|
-
version: 3.
|
|
70
|
+
version: '3.3'
|
|
78
71
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
79
72
|
requirements:
|
|
80
73
|
- - ">="
|
data/.github/workflows/ci.yml
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
name: CI
|
|
2
|
-
on:
|
|
3
|
-
pull_request:
|
|
4
|
-
push:
|
|
5
|
-
branches: [master]
|
|
6
|
-
|
|
7
|
-
permissions:
|
|
8
|
-
contents: read
|
|
9
|
-
pages: write
|
|
10
|
-
id-token: write
|
|
11
|
-
|
|
12
|
-
jobs:
|
|
13
|
-
test:
|
|
14
|
-
name: Ruby ${{ matrix.ruby }} / Rails ${{ matrix.rails }} / AA ${{ matrix.activeadmin }}
|
|
15
|
-
runs-on: ubuntu-latest
|
|
16
|
-
strategy:
|
|
17
|
-
fail-fast: false
|
|
18
|
-
matrix:
|
|
19
|
-
ruby: ['3.2', '3.3', '3.4']
|
|
20
|
-
rails: ['7.1.0', '7.2.0', '8.0.0']
|
|
21
|
-
activeadmin: ['3.2.0', '3.3.0', '3.4.0', '3.5.0']
|
|
22
|
-
exclude:
|
|
23
|
-
- rails: '8.0.0'
|
|
24
|
-
activeadmin: '3.2.0'
|
|
25
|
-
env:
|
|
26
|
-
RAILS: ${{ matrix.rails }}
|
|
27
|
-
AA: ${{ matrix.activeadmin }}
|
|
28
|
-
steps:
|
|
29
|
-
- uses: actions/checkout@v4
|
|
30
|
-
- uses: ruby/setup-ruby@v1
|
|
31
|
-
with:
|
|
32
|
-
ruby-version: ${{ matrix.ruby }}
|
|
33
|
-
bundler-cache: true
|
|
34
|
-
- name: Run tests
|
|
35
|
-
run: bundle exec rspec spec
|
|
36
|
-
|
|
37
|
-
- name: Generate badge.json
|
|
38
|
-
if: matrix.ruby == '3.4' && matrix.rails == '8.0.0' && matrix.activeadmin == '3.5.0'
|
|
39
|
-
run: |
|
|
40
|
-
LAST_RUN="coverage/.last_run.json"
|
|
41
|
-
if [ ! -f "$LAST_RUN" ]; then
|
|
42
|
-
mkdir -p badge
|
|
43
|
-
echo '{"schemaVersion":1,"label":"coverage","message":"unknown","color":"lightgrey"}' > badge/badge.json
|
|
44
|
-
exit 0
|
|
45
|
-
fi
|
|
46
|
-
PERCENT=$(ruby -rjson -e "puts JSON.parse(File.read('$LAST_RUN')).dig('result','line').round(1)")
|
|
47
|
-
PERCENT_NUM=$(ruby -rjson -e "puts JSON.parse(File.read('$LAST_RUN')).dig('result','line')")
|
|
48
|
-
if ruby -e "exit(($PERCENT_NUM >= 90) ? 0 : 1)"; then COLOR="brightgreen"
|
|
49
|
-
elif ruby -e "exit(($PERCENT_NUM >= 75) ? 0 : 1)"; then COLOR="green"
|
|
50
|
-
elif ruby -e "exit(($PERCENT_NUM >= 60) ? 0 : 1)"; then COLOR="yellow"
|
|
51
|
-
else COLOR="red"; fi
|
|
52
|
-
mkdir -p badge
|
|
53
|
-
echo "{\"schemaVersion\":1,\"label\":\"coverage\",\"message\":\"${PERCENT}%\",\"color\":\"${COLOR}\"}" > badge/badge.json
|
|
54
|
-
|
|
55
|
-
- name: Upload badge artifact
|
|
56
|
-
if: matrix.ruby == '3.4' && matrix.rails == '8.0.0' && matrix.activeadmin == '3.5.0'
|
|
57
|
-
uses: actions/upload-artifact@v4
|
|
58
|
-
with:
|
|
59
|
-
name: coverage-badge
|
|
60
|
-
path: badge
|
|
61
|
-
|
|
62
|
-
deploy-coverage:
|
|
63
|
-
needs: test
|
|
64
|
-
if: github.ref == 'refs/heads/master' && github.event_name == 'push'
|
|
65
|
-
runs-on: ubuntu-latest
|
|
66
|
-
environment:
|
|
67
|
-
name: github-pages
|
|
68
|
-
url: ${{ steps.deployment.outputs.page_url }}
|
|
69
|
-
|
|
70
|
-
steps:
|
|
71
|
-
- name: Download coverage badge
|
|
72
|
-
uses: actions/download-artifact@v4
|
|
73
|
-
with:
|
|
74
|
-
name: coverage-badge
|
|
75
|
-
path: coverage
|
|
76
|
-
|
|
77
|
-
- name: Setup Pages
|
|
78
|
-
uses: actions/configure-pages@v5
|
|
79
|
-
|
|
80
|
-
- name: Upload Pages artifact
|
|
81
|
-
uses: actions/upload-pages-artifact@v3
|
|
82
|
-
with:
|
|
83
|
-
path: coverage
|
|
84
|
-
|
|
85
|
-
- name: Deploy to GitHub Pages
|
|
86
|
-
id: deployment
|
|
87
|
-
uses: actions/deploy-pages@v4
|
data/.gitignore
DELETED
data/CODE_OF_CONDUCT.md
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
# Contributor Code of Conduct
|
|
2
|
-
|
|
3
|
-
As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
|
|
4
|
-
|
|
5
|
-
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
|
|
6
|
-
|
|
7
|
-
Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
|
|
8
|
-
|
|
9
|
-
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
|
|
10
|
-
|
|
11
|
-
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
|
|
12
|
-
|
|
13
|
-
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
|
data/Gemfile
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
source 'https://rubygems.org'
|
|
2
|
-
gemspec
|
|
3
|
-
|
|
4
|
-
default_rails_version = '7.1.0'
|
|
5
|
-
default_activeadmin_version = '3.5.0'
|
|
6
|
-
|
|
7
|
-
gem 'rails', "~> #{ENV['RAILS'] || default_rails_version}"
|
|
8
|
-
gem 'activeadmin', "~> #{ENV['AA'] || default_activeadmin_version}"
|
|
9
|
-
gem 'sprockets-rails'
|
|
10
|
-
gem 'sass-rails'
|
|
11
|
-
|
|
12
|
-
group :test do
|
|
13
|
-
gem 'simplecov', require: false
|
|
14
|
-
gem 'rspec-rails'
|
|
15
|
-
gem 'sqlite3', '~> 2.0'
|
|
16
|
-
gem 'database_cleaner'
|
|
17
|
-
gem 'capybara'
|
|
18
|
-
gem 'cuprite'
|
|
19
|
-
gem 'draper'
|
|
20
|
-
gem 'webrick', require: false
|
|
21
|
-
end
|
data/Rakefile
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
# coding: utf-8
|
|
2
|
-
require File.expand_path('../lib/active_admin_scoped_collection_actions/version', __FILE__)
|
|
3
|
-
|
|
4
|
-
Gem::Specification.new do |spec|
|
|
5
|
-
spec.name = "active_admin_scoped_collection_actions"
|
|
6
|
-
spec.version = ActiveAdminScopedCollectionActions::VERSION
|
|
7
|
-
spec.authors = ["Gena M."]
|
|
8
|
-
spec.email = ["workgena@gmail.com"]
|
|
9
|
-
|
|
10
|
-
spec.summary = %q{scoped_collection actions extension for ActiveAdmin}
|
|
11
|
-
spec.description = %q{Plugin for ActiveAdmin. Provides batch Update and Delete for scoped_collection (Filters + Scope) across all pages.}
|
|
12
|
-
spec.homepage = "https://github.com/activeadmin-plugins/active_admin_scoped_collection_actions"
|
|
13
|
-
spec.license = "MIT"
|
|
14
|
-
|
|
15
|
-
spec.required_ruby_version = '>= 3.1.0'
|
|
16
|
-
|
|
17
|
-
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
18
|
-
spec.bindir = "exe"
|
|
19
|
-
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
20
|
-
spec.require_paths = ["lib"]
|
|
21
|
-
|
|
22
|
-
spec.add_dependency "activeadmin", ">= 3.0", "< 4.0"
|
|
23
|
-
end
|
data/screenshots/pupup.png
DELETED
|
Binary file
|
data/screenshots/sidebar.png
DELETED
|
Binary file
|
data/tasks/test.rake
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
desc 'Creates a test rails app for the specs to run against'
|
|
2
|
-
task :setup do
|
|
3
|
-
require 'rails/version'
|
|
4
|
-
|
|
5
|
-
rails_new_args = %w[
|
|
6
|
-
--skip-turbolinks
|
|
7
|
-
--skip-spring
|
|
8
|
-
--skip-bootsnap
|
|
9
|
-
-m
|
|
10
|
-
spec/support/rails_template.rb
|
|
11
|
-
].join(' ')
|
|
12
|
-
|
|
13
|
-
system "bundle exec rails new spec/rails/rails-#{Rails::VERSION::STRING} #{rails_new_args}"
|
|
14
|
-
end
|