active_admin_scoped_collection_actions 1.0.1 → 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 +237 -119
- 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 +16 -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 +60 -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 +8 -18
- data/.github/workflows/ci.yml +0 -44
- data/.gitignore +0 -15
- data/CODE_OF_CONDUCT.md +0 -13
- data/Gemfile +0 -24
- data/Rakefile +0 -7
- data/active_admin_scoped_collection_actions.gemspec +0 -21
- 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,18 +1,40 @@
|
|
|
1
|
-
[](https://github.com/activeadmin-plugins/active_admin_scoped_collection_actions/actions/workflows/ci.yml)
|
|
2
|
+

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

|
|
9
|
+
|
|
10
|
+

|
|
7
11
|
|
|
8
|
-
|
|
12
|
+
## Contents
|
|
9
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)
|
|
10
30
|
|
|
11
|
-
|
|
31
|
+
## Description
|
|
12
32
|
|
|
13
|
-
This gem
|
|
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.
|
|
14
36
|
|
|
15
|
-
|
|
37
|
+
## Installation
|
|
16
38
|
|
|
17
39
|
Add this line to your application's Gemfile:
|
|
18
40
|
|
|
@@ -25,23 +47,23 @@ gem 'active_admin_scoped_collection_actions', github: 'activeadmin-plugins/activ
|
|
|
25
47
|
|
|
26
48
|
And then execute:
|
|
27
49
|
|
|
28
|
-
```
|
|
29
|
-
|
|
50
|
+
```sh
|
|
51
|
+
bundle
|
|
30
52
|
```
|
|
31
53
|
|
|
32
|
-
Add the following line at the end of
|
|
54
|
+
Add the following line at the end of `app/assets/javascript/active_admin.js`:
|
|
33
55
|
|
|
34
56
|
```javascript
|
|
35
57
|
//= require active_admin_scoped_collection_actions
|
|
36
58
|
```
|
|
37
59
|
|
|
38
|
-
Also include CSS in
|
|
60
|
+
Also include the CSS in `app/assets/stylesheets/active_admin.css.scss`:
|
|
39
61
|
|
|
40
|
-
```
|
|
62
|
+
```scss
|
|
41
63
|
@import "active_admin_scoped_collection_actions";
|
|
42
64
|
```
|
|
43
65
|
|
|
44
|
-
|
|
66
|
+
## Usage
|
|
45
67
|
|
|
46
68
|
Usually you need two standard actions: Delete and Update.
|
|
47
69
|
|
|
@@ -65,37 +87,53 @@ ActiveAdmin.register Post do
|
|
|
65
87
|
end
|
|
66
88
|
```
|
|
67
89
|
|
|
68
|
-
**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.
|
|
69
93
|
|
|
70
94
|
### Update action
|
|
71
95
|
|
|
72
|
-
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:
|
|
73
97
|
|
|
74
98
|
```ruby
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
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
|
|
83
106
|
```
|
|
84
107
|
|
|
85
|
-
In this example Phone model has fields:
|
|
86
|
-
* name - varchar string
|
|
87
|
-
* diagonal - integer(of float)
|
|
88
|
-
* manufactured_at - datetime
|
|
89
|
-
* vendor_id - association "belongs_to :vendor, class_name: 'Vendor', foreign_key: :vendor_id"
|
|
90
|
-
* has_3g - boolean
|
|
108
|
+
In this example the Phone model has fields:
|
|
91
109
|
|
|
92
|
-
|
|
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
|
|
115
|
+
|
|
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.
|
|
120
|
+
|
|
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:
|
|
125
|
+
|
|
126
|
+
```javascript
|
|
127
|
+
$(document).on('mass_update_modal_dialog:after_open', function (event, form) {
|
|
128
|
+
$(form).find('input.my-widget').myWidget();
|
|
129
|
+
});
|
|
130
|
+
```
|
|
93
131
|
|
|
94
|
-
|
|
132
|
+
## Custom actions
|
|
95
133
|
|
|
96
|
-
Example:
|
|
134
|
+
Example: we have a Phone resource with a `manufactured_at` column. We need an action which will erase this date.
|
|
97
135
|
|
|
98
|
-
In ActiveAdmin resource:
|
|
136
|
+
In the ActiveAdmin resource:
|
|
99
137
|
|
|
100
138
|
```ruby
|
|
101
139
|
ActiveAdmin.register Phone do
|
|
@@ -111,13 +149,12 @@ ActiveAdmin.register Phone do
|
|
|
111
149
|
end
|
|
112
150
|
```
|
|
113
151
|
|
|
114
|
-
This simple code will create a new
|
|
115
|
-
|
|
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.
|
|
116
154
|
|
|
117
|
-
|
|
155
|
+
## Details and settings
|
|
118
156
|
|
|
119
|
-
|
|
120
|
-
### Why I don't see Sidebar with Collection Actions.
|
|
157
|
+
### Why don't I see the sidebar with collection actions?
|
|
121
158
|
|
|
122
159
|
Sidebar visibility by default depends on several things.
|
|
123
160
|
|
|
@@ -127,19 +164,18 @@ First you must set:
|
|
|
127
164
|
config.batch_actions = true
|
|
128
165
|
```
|
|
129
166
|
|
|
130
|
-
|
|
167
|
+
Internally this gem uses `batch_actions`, so without them collection actions will not work.
|
|
131
168
|
|
|
132
169
|
```ruby
|
|
133
170
|
scoped_collection_action :something_here
|
|
134
171
|
```
|
|
135
172
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
By default we dont allow perform actions on **all** the records. We want protect you from accidental deleting.
|
|
173
|
+
Your resource should have some collection actions. If it does not have any, the sidebar will not appear.
|
|
139
174
|
|
|
140
|
-
|
|
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
|
-
And lastly you can manage sidebar visibility
|
|
178
|
+
And lastly, you can manage sidebar visibility through the resource config:
|
|
143
179
|
|
|
144
180
|
```ruby
|
|
145
181
|
# Always
|
|
@@ -149,85 +185,163 @@ config.scoped_collection_actions_if = -> { params[:scope] }
|
|
|
149
185
|
# etc.
|
|
150
186
|
```
|
|
151
187
|
|
|
152
|
-
|
|
188
|
+
You can also manage the visibility of each action individually:
|
|
189
|
+
|
|
190
|
+
```ruby
|
|
191
|
+
scoped_collection_action :scoped_collection_destroy, if: proc { can? :destroy, Blog }
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Can I use my own handler for the update and delete actions?
|
|
153
195
|
|
|
154
|
-
You can pass block to default
|
|
155
|
-
|
|
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`.
|
|
156
198
|
|
|
157
|
-
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:
|
|
158
201
|
|
|
159
202
|
```ruby
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
|
168
211
|
```
|
|
169
212
|
|
|
213
|
+
### How can I rename a button?
|
|
170
214
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
Every scoped_collection_action has option `:title`.
|
|
215
|
+
Every `scoped_collection_action` has the `:title` option.
|
|
174
216
|
|
|
175
217
|
Example:
|
|
176
218
|
|
|
177
219
|
```ruby
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
220
|
+
scoped_collection_action :erase_date, title: 'Nullify' do
|
|
221
|
+
scoped_collection_records.update_all(manufactured_at: nil)
|
|
222
|
+
end
|
|
223
|
+
```
|
|
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
|
+
```
|
|
232
|
+
|
|
233
|
+
### How can I tell the user how many records are affected?
|
|
234
|
+
|
|
235
|
+
The `:confirm_summary` option adds a sentence with the number of affected records to the dialog:
|
|
236
|
+
|
|
237
|
+
```ruby
|
|
238
|
+
scoped_collection_action :scoped_collection_destroy, confirm_summary: true
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
```text
|
|
242
|
+
Delete all?
|
|
243
|
+
|
|
244
|
+
You are going to delete 100500 record(s).
|
|
245
|
+
|
|
246
|
+
[ OK ] [ Cancel ]
|
|
181
247
|
```
|
|
182
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.
|
|
183
263
|
|
|
184
|
-
|
|
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.
|
|
185
266
|
|
|
186
|
-
|
|
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:
|
|
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:
|
|
187
278
|
|
|
188
279
|
```ruby
|
|
189
|
-
|
|
280
|
+
scoped_collection_action :scoped_collection_update,
|
|
281
|
+
confirm_submit: true,
|
|
282
|
+
form: -> { { body: 'text' } }
|
|
190
283
|
```
|
|
191
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
|
|
192
291
|
|
|
193
|
-
|
|
292
|
+
[ OK ] [ Back ]
|
|
293
|
+
```
|
|
194
294
|
|
|
195
|
-
|
|
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.
|
|
196
298
|
|
|
299
|
+
`true` shows the default message ("Are you sure?"). Pass a String or a Proc to use your own:
|
|
197
300
|
|
|
198
301
|
```ruby
|
|
199
|
-
|
|
302
|
+
scoped_collection_action :scoped_collection_update,
|
|
303
|
+
confirm_submit: 'Update all filtered phones?',
|
|
304
|
+
form: -> { { name: 'text' } }
|
|
200
305
|
```
|
|
201
306
|
|
|
202
|
-
|
|
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:
|
|
203
316
|
|
|
204
317
|
```html
|
|
205
|
-
|
|
318
|
+
<button class="my_popup" data="{"auth_token":"2a+KLu5u9McQENspCiep0DGZI6D09fCVXAN9inrwRG0=","batch_action":"my_pop_action","confirm":"Are you sure?"}">My pop action</button>
|
|
206
319
|
```
|
|
207
320
|
|
|
208
|
-
Without handler, clicking
|
|
321
|
+
Without a handler, clicking the button does nothing.
|
|
209
322
|
|
|
210
323
|
You can render the form in any way you want:
|
|
211
|
-
- It can be some popup(Fancybox, Simplemodal, etc.), or some inline collapsible form.
|
|
212
|
-
- It can even be a separate full-page.
|
|
213
324
|
|
|
214
|
-
|
|
325
|
+
- it can be a popup (Fancybox, Simplemodal, etc.), or an inline collapsible form;
|
|
326
|
+
- it can even be a separate full page.
|
|
215
327
|
|
|
216
|
-
|
|
328
|
+
One thing is important — how you send the data to the server. It should be:
|
|
217
329
|
|
|
218
|
-
|
|
330
|
+
| | |
|
|
331
|
+
| --- | --- |
|
|
332
|
+
| Method | `POST` |
|
|
333
|
+
| URL | `/admin/collection_path/batch_action` |
|
|
334
|
+
| Query string | identical to the current page |
|
|
219
335
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
The easiest way to get them is:
|
|
336
|
+
The easiest way to build it is:
|
|
223
337
|
|
|
224
338
|
```javascript
|
|
225
339
|
url = window.location.pathname + '/batch_action' + window.location.search
|
|
226
340
|
```
|
|
227
341
|
|
|
228
|
-
And
|
|
342
|
+
And the request body params should look like:
|
|
229
343
|
|
|
230
|
-
```
|
|
344
|
+
```text
|
|
231
345
|
changes[manufactured_at] = "2015-07-21 18:11"
|
|
232
346
|
changes[diagonal] = "7"
|
|
233
347
|
changes[some_filed_name]='new value'
|
|
@@ -235,69 +349,73 @@ authenticity_token = "2a+KLu5u9McQENspCiep0DGZI6D09fCVXAN9inrwRG0="
|
|
|
235
349
|
batch_action = "my_pop_action"
|
|
236
350
|
```
|
|
237
351
|
|
|
238
|
-
|
|
239
|
-
|
|
352
|
+
`authenticity_token` and `batch_action` can be taken from the data attribute of the button.
|
|
240
353
|
|
|
241
354
|
Example in JavaScript:
|
|
242
355
|
|
|
243
356
|
```javascript
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
+
});
|
|
253
367
|
```
|
|
254
368
|
|
|
255
|
-
### How notify user about success and
|
|
369
|
+
### How do I notify the user about success and errors?
|
|
256
370
|
|
|
257
|
-
We recommend using Rails
|
|
371
|
+
We recommend using Rails flash messages.
|
|
258
372
|
|
|
259
|
-
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:
|
|
260
374
|
|
|
261
375
|
```ruby
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
376
|
+
class Phone < ActiveRecord::Base
|
|
377
|
+
validates :diagonal, numericality: { only_integer: true }
|
|
378
|
+
end
|
|
265
379
|
```
|
|
266
380
|
|
|
267
381
|
```ruby
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
end
|
|
273
|
-
if errors.empty?
|
|
274
|
-
flash[:notice] = 'Diagonal changed successfully'
|
|
275
|
-
else
|
|
276
|
-
flash[:error] = errors.join('. ')
|
|
277
|
-
end
|
|
278
|
-
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])
|
|
279
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
|
|
280
394
|
```
|
|
281
395
|
|
|
282
|
-
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:
|
|
283
397
|
|
|
284
|
-
```
|
|
398
|
+
```text
|
|
285
399
|
Diagonal must be an integer.
|
|
286
400
|
```
|
|
287
401
|
|
|
288
|
-
But if you use your custom popup, you can show messages with JS.
|
|
289
|
-
|
|
402
|
+
But if you use your own custom popup, you can show messages with JS.
|
|
290
403
|
|
|
291
|
-
### Can I perform action only on selected items?
|
|
404
|
+
### Can I perform an action only on selected items?
|
|
292
405
|
|
|
293
|
-
|
|
406
|
+
The standard index page of a resource with `batch_action` enabled has a selectable column.
|
|
294
407
|
|
|
295
|
-
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:
|
|
296
410
|
|
|
297
411
|
```ruby
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
end
|
|
412
|
+
scoped_collection_action :do_something do
|
|
413
|
+
scoped_collection_records.find_each do |record|
|
|
414
|
+
record.update(name: 'x')
|
|
302
415
|
end
|
|
416
|
+
end
|
|
303
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)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
module ActiveAdminScopedCollectionActions
|
|
2
2
|
module ResourceExtension
|
|
3
|
+
include MethodOrProcHelper
|
|
3
4
|
|
|
4
5
|
def initialize(*)
|
|
5
6
|
super
|
|
@@ -54,6 +55,8 @@ module ActiveAdminScopedCollectionActions
|
|
|
54
55
|
div I18n.t('active_admin_scoped_collection_actions.sidebar_msg')
|
|
55
56
|
|
|
56
57
|
active_admin_config.scoped_collection_actions.each do |key, options={}|
|
|
58
|
+
next if options.key?(:if) && !call_method_or_exec_proc(options[:if])
|
|
59
|
+
|
|
57
60
|
b_title = options.fetch(:title, ::ActiveSupport::Inflector.humanize(key))
|
|
58
61
|
b_options = {}
|
|
59
62
|
b_options[:class] = options[:class] if options[:class].present?
|
|
@@ -65,6 +68,19 @@ module ActiveAdminScopedCollectionActions
|
|
|
65
68
|
b_data[:inputs] = options[:form].is_a?(Proc) ? options[:form].call : options[:form]
|
|
66
69
|
end
|
|
67
70
|
b_data[:confirm] = options.fetch(:confirm, I18n.t('active_admin_scoped_collection_actions.confirm_action_message'))
|
|
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
|
|
68
84
|
b_options[:data] = b_data.to_json
|
|
69
85
|
button b_title, b_options
|
|
70
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,10 +76,14 @@ 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,
|
|
86
|
+
classes: { 'ui-dialog': 'ui-corner-all active_admin_dialog active_admin_dialog_mass_update_by_filter' },
|
|
50
87
|
dialogClass: 'active_admin_dialog active_admin_dialog_mass_update_by_filter',
|
|
51
88
|
maxHeight: window.innerHeight - (window.innerHeight * 0.1),
|
|
52
89
|
open() {
|
|
@@ -60,9 +97,18 @@ ActiveAdmin.dialogMassFieldsUpdate = function(message, inputs, callback){
|
|
|
60
97
|
});
|
|
61
98
|
},
|
|
62
99
|
buttons: {
|
|
63
|
-
OK(
|
|
64
|
-
|
|
65
|
-
|
|
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);
|
|
66
112
|
},
|
|
67
113
|
Cancel() {
|
|
68
114
|
$('.mass_update_protect_fild_flag').off('change');
|
|
@@ -70,4 +116,11 @@ ActiveAdmin.dialogMassFieldsUpdate = function(message, inputs, callback){
|
|
|
70
116
|
}
|
|
71
117
|
}
|
|
72
118
|
});
|
|
119
|
+
|
|
120
|
+
if (summary) {
|
|
121
|
+
ActiveAdmin.dialogSpinnerUntil(summary, summary.data('countRequest'));
|
|
122
|
+
ActiveAdmin.dialogWaitFor(form, summary.data('countRequest'));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return dialog;
|
|
73
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,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: active_admin_scoped_collection_actions
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0
|
|
4
|
+
version: 2.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Gena M.
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: activeadmin
|
|
@@ -16,7 +15,7 @@ dependencies:
|
|
|
16
15
|
requirements:
|
|
17
16
|
- - ">="
|
|
18
17
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '
|
|
18
|
+
version: '3.0'
|
|
20
19
|
- - "<"
|
|
21
20
|
- !ruby/object:Gem::Version
|
|
22
21
|
version: '4.0'
|
|
@@ -26,7 +25,7 @@ dependencies:
|
|
|
26
25
|
requirements:
|
|
27
26
|
- - ">="
|
|
28
27
|
- !ruby/object:Gem::Version
|
|
29
|
-
version: '
|
|
28
|
+
version: '3.0'
|
|
30
29
|
- - "<"
|
|
31
30
|
- !ruby/object:Gem::Version
|
|
32
31
|
version: '4.0'
|
|
@@ -38,15 +37,9 @@ executables: []
|
|
|
38
37
|
extensions: []
|
|
39
38
|
extra_rdoc_files: []
|
|
40
39
|
files:
|
|
41
|
-
- ".github/workflows/ci.yml"
|
|
42
|
-
- ".gitignore"
|
|
43
40
|
- CHANGELOG.md
|
|
44
|
-
- CODE_OF_CONDUCT.md
|
|
45
|
-
- Gemfile
|
|
46
41
|
- LICENSE.txt
|
|
47
42
|
- README.md
|
|
48
|
-
- Rakefile
|
|
49
|
-
- active_admin_scoped_collection_actions.gemspec
|
|
50
43
|
- config/locales/de.yml
|
|
51
44
|
- config/locales/en.yml
|
|
52
45
|
- config/locales/es.yml
|
|
@@ -58,17 +51,15 @@ files:
|
|
|
58
51
|
- lib/active_admin_scoped_collection_actions/engine.rb
|
|
59
52
|
- lib/active_admin_scoped_collection_actions/resource_extension.rb
|
|
60
53
|
- lib/active_admin_scoped_collection_actions/version.rb
|
|
61
|
-
- screenshots/pupup.png
|
|
62
|
-
- screenshots/sidebar.png
|
|
63
|
-
- tasks/test.rake
|
|
64
54
|
- vendor/assets/javascripts/active_admin_scoped_collection_actions.js
|
|
55
|
+
- vendor/assets/javascripts/lib/dialog_confirm_submit.js
|
|
65
56
|
- vendor/assets/javascripts/lib/dialog_mass_fields_update.js
|
|
57
|
+
- vendor/assets/javascripts/lib/dialog_records_summary.js
|
|
66
58
|
- vendor/assets/stylesheets/active_admin_scoped_collection_actions.scss
|
|
67
59
|
homepage: https://github.com/activeadmin-plugins/active_admin_scoped_collection_actions
|
|
68
60
|
licenses:
|
|
69
61
|
- MIT
|
|
70
62
|
metadata: {}
|
|
71
|
-
post_install_message:
|
|
72
63
|
rdoc_options: []
|
|
73
64
|
require_paths:
|
|
74
65
|
- lib
|
|
@@ -76,15 +67,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
76
67
|
requirements:
|
|
77
68
|
- - ">="
|
|
78
69
|
- !ruby/object:Gem::Version
|
|
79
|
-
version: '
|
|
70
|
+
version: '3.3'
|
|
80
71
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
81
72
|
requirements:
|
|
82
73
|
- - ">="
|
|
83
74
|
- !ruby/object:Gem::Version
|
|
84
75
|
version: '0'
|
|
85
76
|
requirements: []
|
|
86
|
-
rubygems_version: 3.
|
|
87
|
-
signing_key:
|
|
77
|
+
rubygems_version: 3.7.1
|
|
88
78
|
specification_version: 4
|
|
89
79
|
summary: scoped_collection actions extension for ActiveAdmin
|
|
90
80
|
test_files: []
|
data/.github/workflows/ci.yml
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
name: CI
|
|
2
|
-
on:
|
|
3
|
-
pull_request:
|
|
4
|
-
push:
|
|
5
|
-
branches:
|
|
6
|
-
- master
|
|
7
|
-
jobs:
|
|
8
|
-
test:
|
|
9
|
-
name: Tests with Ruby ${{ matrix.ruby }} Rails ${{ matrix.rails }} Active Admin ${{ matrix.activeadmin }}
|
|
10
|
-
runs-on: ubuntu-latest
|
|
11
|
-
strategy:
|
|
12
|
-
matrix:
|
|
13
|
-
ruby:
|
|
14
|
-
- '3.0.0'
|
|
15
|
-
- '3.1.0'
|
|
16
|
-
- '3.2.0'
|
|
17
|
-
- '3.3.0'
|
|
18
|
-
rails:
|
|
19
|
-
- '6.1.0'
|
|
20
|
-
- '7.0.0'
|
|
21
|
-
- '7.1.0'
|
|
22
|
-
activeadmin:
|
|
23
|
-
- '2.14.0'
|
|
24
|
-
- '3.0.0'
|
|
25
|
-
- '3.1.0'
|
|
26
|
-
- '3.2.0'
|
|
27
|
-
exclude:
|
|
28
|
-
- rails: '7.1.0'
|
|
29
|
-
activeadmin: '2.14.0'
|
|
30
|
-
- rails: '7.1.0'
|
|
31
|
-
activeadmin: '3.0.0'
|
|
32
|
-
env:
|
|
33
|
-
RAILS: ${{ matrix.rails }}
|
|
34
|
-
AA: ${{ matrix.activeadmin }}
|
|
35
|
-
steps:
|
|
36
|
-
- uses: actions/checkout@v4
|
|
37
|
-
- uses: ruby/setup-ruby@v1
|
|
38
|
-
with:
|
|
39
|
-
ruby-version: ${{ matrix.ruby }}
|
|
40
|
-
- name: Run tests
|
|
41
|
-
run: |
|
|
42
|
-
gem install bundler -v '< 2'
|
|
43
|
-
bundle install
|
|
44
|
-
bundle exec rspec spec
|
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,24 +0,0 @@
|
|
|
1
|
-
source 'https://rubygems.org'
|
|
2
|
-
|
|
3
|
-
gemspec
|
|
4
|
-
|
|
5
|
-
group :test do
|
|
6
|
-
default_rails_version = '7.1.0'
|
|
7
|
-
default_activeadmin_version = '3.2.0'
|
|
8
|
-
|
|
9
|
-
gem 'rails', "~> #{ENV['RAILS'] || default_rails_version}"
|
|
10
|
-
gem 'activeadmin', "~> #{ENV['AA'] || default_activeadmin_version}"
|
|
11
|
-
|
|
12
|
-
gem 'sprockets-rails'
|
|
13
|
-
gem 'rspec-rails'
|
|
14
|
-
gem 'coveralls_reborn', require: false
|
|
15
|
-
gem 'sass-rails'
|
|
16
|
-
gem 'sqlite3', '~> 1.4.0'
|
|
17
|
-
gem 'launchy'
|
|
18
|
-
gem 'database_cleaner'
|
|
19
|
-
gem 'capybara'
|
|
20
|
-
gem 'webdrivers'
|
|
21
|
-
gem 'byebug'
|
|
22
|
-
gem 'draper'
|
|
23
|
-
gem 'webrick', require: false
|
|
24
|
-
end
|
data/Rakefile
DELETED
|
@@ -1,21 +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.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
16
|
-
spec.bindir = "exe"
|
|
17
|
-
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
18
|
-
spec.require_paths = ["lib"]
|
|
19
|
-
|
|
20
|
-
spec.add_dependency "activeadmin", ">= 1.1", "< 4.0"
|
|
21
|
-
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
|