active_admin_scoped_collection_actions 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.travis.yml +9 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +17 -0
- data/LICENSE.txt +21 -0
- data/README.md +300 -0
- data/Rakefile +7 -0
- data/active_admin_scoped_collection_actions.gemspec +22 -0
- data/lib/active_admin_scoped_collection_actions.rb +11 -0
- data/lib/active_admin_scoped_collection_actions/authorization.rb +8 -0
- data/lib/active_admin_scoped_collection_actions/controller.rb +8 -0
- data/lib/active_admin_scoped_collection_actions/dsl.rb +76 -0
- data/lib/active_admin_scoped_collection_actions/engine.rb +9 -0
- data/lib/active_admin_scoped_collection_actions/resource_extension.rb +75 -0
- data/lib/active_admin_scoped_collection_actions/version.rb +3 -0
- data/screenshots/pupup.png +0 -0
- data/screenshots/sidebar.png +0 -0
- data/tasks/test.rake +6 -0
- data/vendor/assets/javascripts/active_admin_scoped_collection_actions.js.coffee +25 -0
- data/vendor/assets/javascripts/lib/dialog_mass_fields_update.js.coffee +54 -0
- data/vendor/assets/stylesheets/active_admin_scoped_collection_actions.scss +34 -0
- metadata +94 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a65b49d4828e78148e804d19a9b15bfaa9071027
|
4
|
+
data.tar.gz: ec99d5cd551fc53b4908a3fecdc0fcedce8446e0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 83339dcb013111f69603215ee092e1e326b688ba6ecdc7daa1710719548a651777c70c7b4edc97ac037698b4e9bcfa17e1eb954322b0dc38682f4f6d1b02abf7
|
7
|
+
data.tar.gz: c06b050bc9577e7d6bb1e1831968926c246e73f64748d24b743531aa77fb118b22e4a706e8beeeea1e1c72f52c1c5e3187846d3628627e04b65351307478e4f6
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,13 @@
|
|
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
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in activeadmin_scoped_collection_actions.gemspec
|
4
|
+
gemspec
|
5
|
+
group :test do
|
6
|
+
gem 'sprockets-rails', '2.3.3'
|
7
|
+
gem 'rails', '4.2.0'
|
8
|
+
gem 'rspec-rails'
|
9
|
+
gem 'activeadmin', github: 'activeadmin', ref: '64b5295571400c461376cf060dae9522731fe6d9'
|
10
|
+
gem 'sass-rails'
|
11
|
+
gem 'sqlite3'
|
12
|
+
gem 'launchy'
|
13
|
+
gem 'database_cleaner'
|
14
|
+
gem 'capybara'
|
15
|
+
gem 'selenium-webdriver'
|
16
|
+
gem 'poltergeist'
|
17
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Gena M.
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,300 @@
|
|
1
|
+
[![Build Status](https://img.shields.io/travis/activeadmin-plugins/active_admin_scoped_collection_actions.svg)](https://travis-ci.org/activeadmin-plugins/active_admin_scoped_collection_actions)
|
2
|
+
|
3
|
+
# ActiveAdmin Scoped Collection Actions
|
4
|
+
Plugin for ActiveAdmin. Provides batch Update and Delete for scoped_collection (Filters + Scope) across all pages.
|
5
|
+
|
6
|
+
![Step 1](/screenshots/sidebar.png)
|
7
|
+
|
8
|
+
![Step 1](/screenshots/pupup.png)
|
9
|
+
|
10
|
+
|
11
|
+
# Description
|
12
|
+
|
13
|
+
This gem give you ability to perform various batch actions on any filtered (or scoped) resource. Action applies to all records across all pages. It is similar to ActiveAdmin batch action, but affects all filtered records. This is usefull if you want to delete or update a lot of records in one click.
|
14
|
+
|
15
|
+
# Install
|
16
|
+
|
17
|
+
Add this line to your application's Gemfile:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
gem 'active_admin_scoped_collection_actions', github: 'activeadmin-plugins/active_admin_scoped_collection_actions'
|
21
|
+
```
|
22
|
+
|
23
|
+
And then execute:
|
24
|
+
|
25
|
+
```
|
26
|
+
$ bundle
|
27
|
+
```
|
28
|
+
|
29
|
+
Add the following line at the end of "app/assets/javascript/active_admin.js.coffee":
|
30
|
+
|
31
|
+
```javascript
|
32
|
+
//= require active_admin_scoped_collection_actions
|
33
|
+
```
|
34
|
+
|
35
|
+
Also include CSS in "app/assets/stylesheets/active_admin.css.scss"
|
36
|
+
|
37
|
+
```css
|
38
|
+
@import "active_admin_scoped_collection_actions";
|
39
|
+
```
|
40
|
+
|
41
|
+
# Usage
|
42
|
+
|
43
|
+
Usually you need two standard actions: Delete and Update.
|
44
|
+
|
45
|
+
For example, if you have resource Posts and you want to have a delete action, add:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
scoped_collection_action :scoped_collection_destroy
|
49
|
+
```
|
50
|
+
|
51
|
+
Example:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
ActiveAdmin.register Post do
|
55
|
+
config.batch_actions = true
|
56
|
+
|
57
|
+
scoped_collection_action :scoped_collection_destroy
|
58
|
+
|
59
|
+
index do
|
60
|
+
# ...
|
61
|
+
end
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
**Important**: Visit Posts page with your browser and you will see no changes. Now, perform any filter with the Filters sidebar. Only after you filter will you see a delete button. It will be in sidebar under Filters.
|
66
|
+
|
67
|
+
### Update action
|
68
|
+
|
69
|
+
Update is second standard action and is more complex. It has "form" hash wrapped in Proc:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
scoped_collection_action :scoped_collection_update, form: -> do
|
73
|
+
{ name: 'text',
|
74
|
+
diagonal: 'text',
|
75
|
+
manufactured_at: 'datepicker',
|
76
|
+
vendor_id: Vendor.all.map { |region| [region.name, region.id] },
|
77
|
+
has_3g: [['Yes', 't'], ['No', 'f']]
|
78
|
+
}
|
79
|
+
end
|
80
|
+
```
|
81
|
+
|
82
|
+
In this example Phone model has fields:
|
83
|
+
* name - varchar string
|
84
|
+
* diagonal - integer(of float)
|
85
|
+
* manufactured_at - datetime
|
86
|
+
* vendor_id - association "belongs_to :vendor, class_name: 'Vendor', foreign_key: :vendor_id"
|
87
|
+
* has_3g - boolean
|
88
|
+
|
89
|
+
Parameter "form" is a proc object which returns Hash. It defines what fields you want to be able to update. Hash keys are column names in database. Hash values are a types of HTML inputs. We support only "text", "datepicker" and "selectbox". If you want something more complex - you can build your own forms.
|
90
|
+
|
91
|
+
# Custom Actions
|
92
|
+
|
93
|
+
Example: We have Phone resource and it has column "manufactured_at". We need an action which will erase this date.
|
94
|
+
|
95
|
+
In ActiveAdmin resource:
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
ActiveAdmin.register Phone do
|
99
|
+
config.batch_actions = true
|
100
|
+
|
101
|
+
scoped_collection_action :erase_date do
|
102
|
+
scoped_collection_records.update_all(manufactured_at: nil)
|
103
|
+
end
|
104
|
+
|
105
|
+
index do
|
106
|
+
# ...
|
107
|
+
end
|
108
|
+
end
|
109
|
+
```
|
110
|
+
|
111
|
+
This simple code will create a new button "Erase date" in sidebar. After clicking this button, the user will see confirm message "Are you sure?". After confirming, all filtered records will be updated.
|
112
|
+
|
113
|
+
|
114
|
+
# Details and Settings
|
115
|
+
|
116
|
+
|
117
|
+
### Why I don't see Sidebar with Collection Actions.
|
118
|
+
|
119
|
+
Sidebar visibility by default depends on several things.
|
120
|
+
|
121
|
+
First you must set:
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
config.batch_actions = true
|
125
|
+
```
|
126
|
+
|
127
|
+
Actually, inside of this Gem we use "batch_actions". So without them Collection Actions wouldn't work.
|
128
|
+
|
129
|
+
```ruby
|
130
|
+
scoped_collection_action :something_here
|
131
|
+
```
|
132
|
+
|
133
|
+
You resource should have some collection actions. If it doesn't have any, the sidebar will not appear.
|
134
|
+
|
135
|
+
By default we dont allow perform actions on **all** the records. We want protect you from accidental deleting.
|
136
|
+
|
137
|
+
Sidebar with buttons will appear only after you perform filtering or scopes on resource records.
|
138
|
+
|
139
|
+
And lastly you can manage sidebar visibility by resource config:
|
140
|
+
|
141
|
+
```ruby
|
142
|
+
# Always
|
143
|
+
config.scoped_collection_actions_if = -> { true }
|
144
|
+
# Only for scopes
|
145
|
+
config.scoped_collection_actions_if = -> { params[:scope] }
|
146
|
+
# etc.
|
147
|
+
```
|
148
|
+
|
149
|
+
### Can I use my handler on update/delete action?
|
150
|
+
|
151
|
+
You can pass block to default actions update and delete.
|
152
|
+
And do custom redirect after it. Use `render` (location: 'something') instead of `redirect_to()`.
|
153
|
+
|
154
|
+
This example renders form which allows to change `name` field. And after it do redirect to dashboard page.
|
155
|
+
|
156
|
+
```ruby
|
157
|
+
scoped_collection_action :scoped_collection_update,
|
158
|
+
form: -> {
|
159
|
+
{name: 'text'}
|
160
|
+
} do
|
161
|
+
scoped_collection_records.update_all(name: params[:changes][:name])
|
162
|
+
flash[:notice] = 'Name successfully changed.'
|
163
|
+
render nothing: true, status: :no_content, location: admin_dashboard_path
|
164
|
+
end
|
165
|
+
```
|
166
|
+
|
167
|
+
|
168
|
+
### How can I rename button?
|
169
|
+
|
170
|
+
Every scoped_collection_action has option `:title`.
|
171
|
+
|
172
|
+
Example:
|
173
|
+
|
174
|
+
```ruby
|
175
|
+
scoped_collection_action :erase_date, title: 'Nullify' do
|
176
|
+
scoped_collection_records.update_all(manufactured_at: nil)
|
177
|
+
end
|
178
|
+
```
|
179
|
+
|
180
|
+
|
181
|
+
### How can I modify modal dialog title?
|
182
|
+
|
183
|
+
Similar to button title. Use option `:confirm`
|
184
|
+
|
185
|
+
```ruby
|
186
|
+
scoped_collection_action :scoped_collection_destroy, confirm: 'Delete all phones?'
|
187
|
+
```
|
188
|
+
|
189
|
+
|
190
|
+
### Can I replace you pop-up form with my own?
|
191
|
+
|
192
|
+
Yes. But also you must take care of mandatory parameters passed to the server.
|
193
|
+
|
194
|
+
|
195
|
+
```ruby
|
196
|
+
scoped_collection_action :my_pop_action, class: 'my_popup'
|
197
|
+
```
|
198
|
+
|
199
|
+
Now in HTML page, you have button:
|
200
|
+
|
201
|
+
```html
|
202
|
+
<button class="my_popup" data="{"auth_token":"2a+KLu5u9McQENspCiep0DGZI6D09fCVXAN9inrwRG0=","batch_action":"my_pop_action","confirm":"Are you sure?"}">My pop action</button>
|
203
|
+
```
|
204
|
+
|
205
|
+
Without handler, clicking on the button does nothing.
|
206
|
+
|
207
|
+
You can render the form in any way you want:
|
208
|
+
- It can be some popup(Fancybox, Simplemodal, etc.), or some inline collapsible form.
|
209
|
+
- It can even be a separate full-page.
|
210
|
+
|
211
|
+
One thing is important - how you will send data to server. Generally it should be:
|
212
|
+
|
213
|
+
POST request
|
214
|
+
|
215
|
+
URL: /admin/collection_path/batch_action
|
216
|
+
|
217
|
+
with GET params identical to current page
|
218
|
+
|
219
|
+
The easiest way to get them is:
|
220
|
+
|
221
|
+
```javascript
|
222
|
+
url = window.location.pathname + '/batch_action' + window.location.search
|
223
|
+
```
|
224
|
+
|
225
|
+
And Request body params should be like:
|
226
|
+
|
227
|
+
```
|
228
|
+
changes[manufactured_at] = "2015-07-21 18:11"
|
229
|
+
changes[diagonal] = "7"
|
230
|
+
changes[some_filed_name]='new value'
|
231
|
+
authenticity_token = "2a+KLu5u9McQENspCiep0DGZI6D09fCVXAN9inrwRG0="
|
232
|
+
batch_action = "my_pop_action"
|
233
|
+
```
|
234
|
+
|
235
|
+
```authenticity_token``` and ```batch_action``` you can get from data-attribute of the Button.
|
236
|
+
|
237
|
+
|
238
|
+
Example in JavaScript:
|
239
|
+
|
240
|
+
```javascript
|
241
|
+
url = window.location.pathname + '/batch_action' + window.location.search
|
242
|
+
form_data = {
|
243
|
+
changes: {"manufactured_at": "2015-07-21 18:11", "diagonal": "7"},
|
244
|
+
collection_selection: [],
|
245
|
+
authenticity_token: "2a+KLu5u9McQENspCiep0DGZI6D09fCVXAN9inrwRG0=",
|
246
|
+
batch_action: "my_pop_action"
|
247
|
+
}
|
248
|
+
$.post(url, form_data).always () ->
|
249
|
+
window.location.reload()
|
250
|
+
```
|
251
|
+
|
252
|
+
### How notify user about success and error operations?
|
253
|
+
|
254
|
+
We recommend using Rails Flash messages.
|
255
|
+
|
256
|
+
Example with updating phone diagonal attribute. In this case model Phone has validation:
|
257
|
+
|
258
|
+
```ruby
|
259
|
+
class Phone < ActiveRecord::Base
|
260
|
+
validates :diagonal, numericality: { only_integer: true }
|
261
|
+
end
|
262
|
+
```
|
263
|
+
|
264
|
+
```ruby
|
265
|
+
scoped_collection_action :change_diagonal, form: { diagonal: 'text' } do
|
266
|
+
errors = []
|
267
|
+
scoped_collection_records.find_each do |record|
|
268
|
+
errors << "#{record.errors.full_messages.join('. ')}" unless record.update(diagonal: params[:changes][:diagonal])
|
269
|
+
end
|
270
|
+
if errors.empty?
|
271
|
+
flash[:notice] = 'Diagonal changed successfully'
|
272
|
+
else
|
273
|
+
flash[:error] = errors.join('. ')
|
274
|
+
end
|
275
|
+
render nothing: true, status: :no_content
|
276
|
+
end
|
277
|
+
```
|
278
|
+
|
279
|
+
When you try to update diagonal with "5.6" you will see flash error:
|
280
|
+
|
281
|
+
```
|
282
|
+
Diagonal must be an integer.
|
283
|
+
```
|
284
|
+
|
285
|
+
But if you use your custom popup, you can show messages with JS.
|
286
|
+
|
287
|
+
|
288
|
+
### Can I perform action only on selected items?
|
289
|
+
|
290
|
+
Standard index-page of a resource with batch_action enabled has selectable column.
|
291
|
+
|
292
|
+
If you checked some items and parform any Collection Action, the handler will take care of it. If you write custom actions, you should do like this:
|
293
|
+
|
294
|
+
```ruby
|
295
|
+
scoped_collection_action :do_something do
|
296
|
+
scoped_collection_records.find_each do |record|
|
297
|
+
record.update(name: 'x')
|
298
|
+
end
|
299
|
+
end
|
300
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
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_development_dependency "bundler", "~> 1.8"
|
21
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
22
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'activeadmin'
|
2
|
+
require 'active_admin_scoped_collection_actions/engine'
|
3
|
+
require 'active_admin_scoped_collection_actions/version'
|
4
|
+
require 'active_admin_scoped_collection_actions/dsl'
|
5
|
+
require 'active_admin_scoped_collection_actions/resource_extension'
|
6
|
+
require 'active_admin_scoped_collection_actions/controller'
|
7
|
+
require 'active_admin_scoped_collection_actions/authorization'
|
8
|
+
|
9
|
+
ActiveAdmin::ResourceDSL.send :include, ActiveAdminScopedCollectionActions::DSL
|
10
|
+
ActiveAdmin::Resource.send :include, ActiveAdminScopedCollectionActions::ResourceExtension
|
11
|
+
ActiveAdmin::Authorization.send :include, ActiveAdminScopedCollectionActions::Authorization
|
@@ -0,0 +1,8 @@
|
|
1
|
+
module ActiveAdminScopedCollectionActions
|
2
|
+
module Controller
|
3
|
+
def scoped_collection_records
|
4
|
+
selection = params.fetch(:collection_selection, [])
|
5
|
+
selection.any? ? batch_action_collection.where(resource_class.primary_key => selection) : batch_action_collection
|
6
|
+
end
|
7
|
+
end
|
8
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module ActiveAdminScopedCollectionActions
|
2
|
+
module DSL
|
3
|
+
|
4
|
+
def scoped_collection_action(name, options = {}, &block)
|
5
|
+
if name == :scoped_collection_destroy
|
6
|
+
options[:title] = 'Delete batch' if options[:title].nil?
|
7
|
+
add_scoped_collection_action_default_destroy(options, &block)
|
8
|
+
elsif name == :scoped_collection_update
|
9
|
+
options[:title] = 'Update batch' if options[:title].nil?
|
10
|
+
add_scoped_collection_action_default_update(options, &block)
|
11
|
+
else
|
12
|
+
batch_action(name, if: proc { false }, &block)
|
13
|
+
end
|
14
|
+
# sidebar button
|
15
|
+
config.add_scoped_collection_action(name, options)
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
def add_scoped_collection_action_default_update(options, &block)
|
20
|
+
batch_action :scoped_collection_update, if: proc { false } do
|
21
|
+
unless authorized?(:batch_edit, resource_class)
|
22
|
+
flash[:error] = 'Access denied'
|
23
|
+
render nothing: true, status: :no_content and next
|
24
|
+
end
|
25
|
+
if !params.has_key?(:changes) || params[:changes].empty?
|
26
|
+
render nothing: true, status: :no_content and next
|
27
|
+
end
|
28
|
+
permitted_changes = params.require(:changes).permit(*(options[:form].call.keys))
|
29
|
+
if block_given?
|
30
|
+
instance_eval &block
|
31
|
+
else
|
32
|
+
errors = []
|
33
|
+
scoped_collection_records.find_each do |record|
|
34
|
+
unless update_resource(record, [permitted_changes])
|
35
|
+
errors << "#{record.attributes[resource_class.primary_key]} | #{record.errors.full_messages.join('. ')}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
if errors.empty?
|
39
|
+
flash[:notice] = 'Batch update done'
|
40
|
+
else
|
41
|
+
flash[:error] = errors.join(". ")
|
42
|
+
end
|
43
|
+
render nothing: true, status: :no_content
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
def add_scoped_collection_action_default_destroy(_, &block)
|
50
|
+
batch_action :scoped_collection_destroy, if: proc { false } do |_|
|
51
|
+
unless authorized?(:batch_destroy, resource_class)
|
52
|
+
flash[:error] = 'Access denied'
|
53
|
+
render nothing: true, status: :no_content and next
|
54
|
+
end
|
55
|
+
if block_given?
|
56
|
+
instance_eval &block
|
57
|
+
else
|
58
|
+
errors = []
|
59
|
+
scoped_collection_records.find_each do |record|
|
60
|
+
unless destroy_resource(record)
|
61
|
+
errors << "#{record.attributes[resource_class.primary_key]} | Cant be destroyed}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
if errors.empty?
|
65
|
+
flash[:notice] = 'Batch destroy done'
|
66
|
+
else
|
67
|
+
flash[:error] = errors.join(". ")
|
68
|
+
end
|
69
|
+
render nothing: true, status: :no_content
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module ActiveAdminScopedCollectionActions
|
2
|
+
module ResourceExtension
|
3
|
+
|
4
|
+
def initialize(*)
|
5
|
+
super
|
6
|
+
add_scoped_collection_actions_sidebar_section
|
7
|
+
end
|
8
|
+
|
9
|
+
def scoped_collection_actions
|
10
|
+
@scoped_collection_actions || {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def scoped_collection_actions_on_all=(bool)
|
14
|
+
@scoped_collection_actions_unconditionally = bool
|
15
|
+
end
|
16
|
+
|
17
|
+
def scoped_collection_actions_on_all
|
18
|
+
@scoped_collection_actions_unconditionally || false
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_scoped_collection_action(name, options)
|
22
|
+
(@scoped_collection_actions ||= {})[name.to_sym] = options
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_scoped_collection_actions_sidebar_section
|
26
|
+
self.sidebar_sections << scoped_collection_actions_sidebar_section
|
27
|
+
end
|
28
|
+
|
29
|
+
def scoped_collection_actions_if=(if_proc)
|
30
|
+
@if_proc = if_proc
|
31
|
+
end
|
32
|
+
|
33
|
+
def scoped_collection_actions_if
|
34
|
+
@if_proc
|
35
|
+
end
|
36
|
+
|
37
|
+
def scoped_collection_sidebar_condition
|
38
|
+
-> do
|
39
|
+
if active_admin_config.scoped_collection_actions_if.is_a?(Proc)
|
40
|
+
instance_exec &active_admin_config.scoped_collection_actions_if
|
41
|
+
else
|
42
|
+
filtered_scoped = (params[:q] || params[:scope])
|
43
|
+
on_all = active_admin_config.scoped_collection_actions_on_all
|
44
|
+
has_actions = active_admin_config.scoped_collection_actions.any?
|
45
|
+
batch_actions_enabled = active_admin_config.batch_actions_enabled?
|
46
|
+
( batch_actions_enabled && has_actions && (filtered_scoped || on_all) )
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def scoped_collection_actions_sidebar_section
|
52
|
+
ActiveAdmin::SidebarSection.new :collection_actions, only: :index, if: scoped_collection_sidebar_condition do
|
53
|
+
|
54
|
+
div 'This batch operations affect selected records. Or if none is selected, it will involve all records by current filters and scopes.'
|
55
|
+
|
56
|
+
active_admin_config.scoped_collection_actions.each do |key, options={}|
|
57
|
+
b_title = options.fetch(:title, ::ActiveSupport::Inflector.humanize(key))
|
58
|
+
b_options = {}
|
59
|
+
b_options[:class] = options[:class] if options[:class].present?
|
60
|
+
# Important: If user did not specify html_class, then use default
|
61
|
+
b_options[:class] = 'scoped_collection_action_button' unless b_options[:class]
|
62
|
+
b_data = { auth_token: form_authenticity_token.to_s }
|
63
|
+
b_data[:batch_action] = key.to_s
|
64
|
+
if options[:form].present?
|
65
|
+
b_data[:inputs] = options[:form].is_a?(Proc) ? options[:form].call : options[:form]
|
66
|
+
end
|
67
|
+
b_data[:confirm] = options.fetch(:confirm, 'Are you sure?')
|
68
|
+
b_options[:data] = b_data.to_json
|
69
|
+
button b_title, b_options
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
Binary file
|
Binary file
|
data/tasks/test.rake
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
desc "Creates a test rails app for the specs to run against"
|
2
|
+
task :setup do
|
3
|
+
require 'rails/version'
|
4
|
+
system("mkdir spec/rails") unless File.exists?("spec/rails")
|
5
|
+
system "bundle exec rails new spec/rails/rails-#{Rails::VERSION::STRING} -m spec/support/rails_template.rb --skip-spring"
|
6
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
#= require ./lib/dialog_mass_fields_update
|
2
|
+
|
3
|
+
$(document).ready ->
|
4
|
+
|
5
|
+
$('.scoped_collection_action_button').click (e) ->
|
6
|
+
e.preventDefault()
|
7
|
+
fields = JSON.parse( $(this).attr('data') )
|
8
|
+
|
9
|
+
ActiveAdmin.dialogMassFieldsUpdate fields['confirm'], fields['inputs'],
|
10
|
+
(inputs)=>
|
11
|
+
url = window.location.pathname + '/batch_action' + window.location.search
|
12
|
+
form_data = {
|
13
|
+
changes: inputs,
|
14
|
+
collection_selection: [],
|
15
|
+
authenticity_token: fields['auth_token'],
|
16
|
+
batch_action: fields['batch_action']
|
17
|
+
}
|
18
|
+
$('.paginated_collection').find('input.collection_selection:checked').each (i, el) ->
|
19
|
+
form_data["collection_selection"].push($(el).val())
|
20
|
+
|
21
|
+
$.post(url, form_data).always (data, textStatus, jqXHR) ->
|
22
|
+
if jqXHR.getResponseHeader('Location')
|
23
|
+
window.location.assign jqXHR.getResponseHeader('Location')
|
24
|
+
else
|
25
|
+
window.location.reload()
|
@@ -0,0 +1,54 @@
|
|
1
|
+
ActiveAdmin.dialogMassFieldsUpdate = (message, inputs, callback)->
|
2
|
+
html = """<form id="dialog_confirm" title="#{message}"><div stype="padding-right:4px;padding-left:1px;margin-right:2px"><ul>"""
|
3
|
+
for name, type of inputs
|
4
|
+
if /^(datepicker|checkbox|text)$/.test type
|
5
|
+
wrapper = 'input'
|
6
|
+
else if $.isArray type
|
7
|
+
[wrapper, elem, opts, type] = ['select', 'option', type, '']
|
8
|
+
else
|
9
|
+
throw new Error "Unsupported input type: {#{name}: #{type}}"
|
10
|
+
|
11
|
+
klass = if type is 'datepicker' then type else ''
|
12
|
+
html += """<li>
|
13
|
+
<input type='checkbox' class='mass_update_protect_fild_flag' value='Y' id="mass_update_dialog_#{name}" />
|
14
|
+
<label for="mass_update_dialog_#{name}"> #{name.charAt(0).toUpperCase() + name.slice(1)}</label>
|
15
|
+
<#{wrapper} name="#{name}" class="#{klass}" type="#{type}" disabled="disabled">""" +
|
16
|
+
(if opts then (
|
17
|
+
for v in opts
|
18
|
+
$elem = $("<#{elem}/>")
|
19
|
+
if $.isArray v
|
20
|
+
$elem.text(v[0]).val(v[1])
|
21
|
+
else
|
22
|
+
$elem.text(v)
|
23
|
+
$elem.wrap('<div>').parent().html()
|
24
|
+
).join '' else '')
|
25
|
+
if wrapper == 'select'
|
26
|
+
html += "</#{wrapper}>"
|
27
|
+
html += "</li>"
|
28
|
+
|
29
|
+
[wrapper, elem, opts, type, klass] = [] # unset any temporary variables
|
30
|
+
|
31
|
+
html += "</ul></div></form>"
|
32
|
+
|
33
|
+
form = $(html).appendTo('body')
|
34
|
+
|
35
|
+
$('body').trigger 'mass_update_modal_dialog:before_open', [form]
|
36
|
+
|
37
|
+
form.dialog
|
38
|
+
modal: true
|
39
|
+
dialogClass: 'active_admin_dialog active_admin_dialog_mass_update_by_filter',
|
40
|
+
maxHeight: window.innerHeight - window.innerHeight * 0.1,
|
41
|
+
open: ->
|
42
|
+
$('body').trigger 'mass_update_modal_dialog:after_open', [form]
|
43
|
+
$('.mass_update_protect_fild_flag').on 'change', (e) ->
|
44
|
+
if this.checked
|
45
|
+
$(e.target).next().next().removeAttr('disabled').trigger("chosen:updated")
|
46
|
+
else
|
47
|
+
$(e.target).next().next().attr('disabled', 'disabled').trigger("chosen:updated")
|
48
|
+
buttons:
|
49
|
+
OK: (e)->
|
50
|
+
$(e.target).closest('.ui-dialog-buttonset').html('<span>Processing. Please wait...</span>')
|
51
|
+
callback $(@).serializeObject()
|
52
|
+
Cancel: ->
|
53
|
+
$('.mass_update_protect_fild_flag').off('change')
|
54
|
+
$(@).dialog('close').remove()
|
@@ -0,0 +1,34 @@
|
|
1
|
+
.active_admin_dialog_mass_update_by_filter {
|
2
|
+
form {
|
3
|
+
overflow-y: scroll;
|
4
|
+
|
5
|
+
li {
|
6
|
+
display: block;
|
7
|
+
margin: 10px 0 0 0;
|
8
|
+
}
|
9
|
+
input[type=text], input[type=datepicker], select {
|
10
|
+
margin-top: 3px;
|
11
|
+
display: block;
|
12
|
+
}
|
13
|
+
input[type=text], input[type=datepicker] {
|
14
|
+
width: calc(100% - 20px);
|
15
|
+
padding: 8px 10px 7px;
|
16
|
+
}
|
17
|
+
select {
|
18
|
+
width: 100%;
|
19
|
+
}
|
20
|
+
}
|
21
|
+
button {
|
22
|
+
cursor: pointer;
|
23
|
+
}
|
24
|
+
}
|
25
|
+
|
26
|
+
.sidebar_section.panel {
|
27
|
+
.scoped_collection_action_button {
|
28
|
+
cursor: pointer;
|
29
|
+
margin: 10px 5px 0 0;
|
30
|
+
&:last-child {
|
31
|
+
margin-right: 0;
|
32
|
+
}
|
33
|
+
}
|
34
|
+
}
|
metadata
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: active_admin_scoped_collection_actions
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Gena M.
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-04-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.8'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.8'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
description: Plugin for ActiveAdmin. Provides batch Update and Delete for scoped_collection
|
42
|
+
(Filters + Scope) across all pages.
|
43
|
+
email:
|
44
|
+
- workgena@gmail.com
|
45
|
+
executables: []
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- ".gitignore"
|
50
|
+
- ".travis.yml"
|
51
|
+
- CODE_OF_CONDUCT.md
|
52
|
+
- Gemfile
|
53
|
+
- LICENSE.txt
|
54
|
+
- README.md
|
55
|
+
- Rakefile
|
56
|
+
- active_admin_scoped_collection_actions.gemspec
|
57
|
+
- lib/active_admin_scoped_collection_actions.rb
|
58
|
+
- lib/active_admin_scoped_collection_actions/authorization.rb
|
59
|
+
- lib/active_admin_scoped_collection_actions/controller.rb
|
60
|
+
- lib/active_admin_scoped_collection_actions/dsl.rb
|
61
|
+
- lib/active_admin_scoped_collection_actions/engine.rb
|
62
|
+
- lib/active_admin_scoped_collection_actions/resource_extension.rb
|
63
|
+
- lib/active_admin_scoped_collection_actions/version.rb
|
64
|
+
- screenshots/pupup.png
|
65
|
+
- screenshots/sidebar.png
|
66
|
+
- tasks/test.rake
|
67
|
+
- vendor/assets/javascripts/active_admin_scoped_collection_actions.js.coffee
|
68
|
+
- vendor/assets/javascripts/lib/dialog_mass_fields_update.js.coffee
|
69
|
+
- vendor/assets/stylesheets/active_admin_scoped_collection_actions.scss
|
70
|
+
homepage: https://github.com/activeadmin-plugins/active_admin_scoped_collection_actions
|
71
|
+
licenses:
|
72
|
+
- MIT
|
73
|
+
metadata: {}
|
74
|
+
post_install_message:
|
75
|
+
rdoc_options: []
|
76
|
+
require_paths:
|
77
|
+
- lib
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
requirements: []
|
89
|
+
rubyforge_project:
|
90
|
+
rubygems_version: 2.6.10
|
91
|
+
signing_key:
|
92
|
+
specification_version: 4
|
93
|
+
summary: scoped_collection actions extension for ActiveAdmin
|
94
|
+
test_files: []
|