active_scaffold_config_list 3.6.3 → 4.0.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/README.md +219 -18
- data/app/assets/javascripts/active_scaffold_config_list.js +41 -0
- data/app/assets/stylesheets/active_scaffold_config_list.scss +66 -0
- data/app/views/active_scaffold_overrides/_list_header.html.erb +2 -0
- data/app/views/active_scaffold_overrides/_named_views.html.erb +14 -0
- data/app/views/active_scaffold_overrides/_show_config_list_form.html.erb +3 -3
- data/app/views/active_scaffold_overrides/_show_config_list_form_body.html.erb +34 -9
- data/config/locales/de.yml +4 -0
- data/config/locales/en.yml +4 -0
- data/config/locales/es.yml +4 -0
- data/config/locales/fr.yml +4 -0
- data/config/locales/hu.yml +4 -0
- data/config/locales/ja.yml +4 -0
- data/config/locales/ru.yml +4 -0
- data/lib/active_scaffold/actions/config_list.rb +132 -32
- data/lib/active_scaffold/config/config_list.rb +59 -2
- data/lib/active_scaffold/data_structures/named_view.rb +40 -0
- data/lib/active_scaffold/helpers/config_list_helpers.rb +67 -0
- data/lib/active_scaffold_config_list/config_listable.rb +112 -0
- data/lib/active_scaffold_config_list/engine.rb +8 -0
- data/lib/active_scaffold_config_list/version.rb +3 -3
- data/lib/active_scaffold_config_list.rb +7 -1
- metadata +10 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2065069db537dffc9b14bbdb46247225138c88b5e521998810333f7d602978ba
|
|
4
|
+
data.tar.gz: 19471cbf086ad1f9ab84f5acf6bcbd3d1c336496f84a13838985f1ae26b72722
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5b14bcf01e3afabb7935cffa6142f159a5dd08d1d90c432976ced5d0d82612ea29315bff85e7e3eb25f4d429b7a25ecb38c11fb217c5ec4e6fa1e5c1b16be759
|
|
7
|
+
data.tar.gz: 495a02db21bb2ab03905f15287b31b7582ac1a02a89d14fdfda287550ca6ef69de2dddd28a55efa162c80026150ed91acb5628ddb88899dd9202f0d29f443d50
|
data/README.md
CHANGED
|
@@ -16,12 +16,13 @@ Overview
|
|
|
16
16
|
========
|
|
17
17
|
|
|
18
18
|
A plugin for Active Scaffold that provides the ability to choose the column to show in the scaffold list at run-time.
|
|
19
|
+
Also, it adds the ability to define named views with other set of columns to choose between the default list view and
|
|
20
|
+
the named views.
|
|
19
21
|
|
|
20
22
|
You have the option of defining a default set of columns for the controller. For example:
|
|
21
23
|
```rb
|
|
22
24
|
config.config_list.default_columns = [:name, :project, :amount]
|
|
23
25
|
```
|
|
24
|
-
If this is not defined then active_scaffold_config.list.columns is used.
|
|
25
26
|
|
|
26
27
|
This is useful when you want the option to look at a potentially large number of columns but be able to
|
|
27
28
|
easily reset back to something that fits on the screen without horizontal scrolling.
|
|
@@ -30,24 +31,61 @@ The available columns in the configure action are the columns defined in `list.c
|
|
|
30
31
|
`config_list.default_columns` is not defined, then it will default to `list.columns`, and the users only will be able
|
|
31
32
|
to remove some columns, they won't be able to add other columns.
|
|
32
33
|
|
|
33
|
-
The configuration data will be saved
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
The configuration data will be saved in the session.
|
|
35
|
+
|
|
36
|
+
## Named Views
|
|
37
|
+
|
|
38
|
+
It's possible to define named views, with a set of columns, so users can switch between default view and other defined views. Adding a view is done with `add_view` method, and passing a name (symbol or string) and an array of columns:
|
|
36
39
|
|
|
37
40
|
```rb
|
|
38
|
-
conf.config_list.
|
|
41
|
+
conf.config_list.add_view :simple, [:number, :status]
|
|
39
42
|
```
|
|
40
43
|
|
|
41
|
-
|
|
44
|
+
Although it's possible to use a block too, to change other view settings, such as label, sorting and security method. If the label is set, it can be a string, or a symbol to be localized, and the name is used in the URL parameters. When using a block, the columns can be defined in the view instead of the `add_view` call:
|
|
42
45
|
|
|
43
46
|
```rb
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
conf.config_list.add_view :simple do |view|
|
|
48
|
+
view.label = 'Number and Status'
|
|
49
|
+
view.columns = [:number, :status]
|
|
50
|
+
view.sorting = {number: :desc}
|
|
51
|
+
view.security_method = :simple_view_authorized?
|
|
52
|
+
end
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The columns defined in the view are not required to be in the `conf.list.columns`, so it's possible to add views including columns that are not available in the normal config list.
|
|
56
|
+
|
|
57
|
+
The security method is a controller method used to check if the view is available for the user, it must return true when the view is allowed:
|
|
58
|
+
|
|
59
|
+
```rb
|
|
60
|
+
def simple_view_authorized?
|
|
61
|
+
current_user.is_admin?
|
|
46
62
|
end
|
|
47
63
|
```
|
|
48
64
|
|
|
49
|
-
|
|
50
|
-
|
|
65
|
+
When a named view is selected, the configure action link is not rendered.
|
|
66
|
+
|
|
67
|
+
The view selector is displayed in the title header, and it's rendered as a menu of links, displaying the selected view, and the list of available views on hover. The selector can be changed to a list of radio buttons or select field with `conf.config_list.named_views_selector`,per controller, or it can be changed globally in `ActiveScaffold.defaults`:
|
|
68
|
+
|
|
69
|
+
```rb
|
|
70
|
+
conf.config_list.named_views_selector = :radio # use radio buttons
|
|
71
|
+
conf.config_list.named_views_selector = :select # use select field
|
|
72
|
+
conf.config_list.named_views_selector = :links # use a menu of links (default)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The position of the views selector can be changed with `conf.config_list.named_views_position`, setting it to `:center` (default, between the title and actions), `:left` (next to the title) or `:right` (at the right of the actions). Although the position of the `div.config-list-views` doesn't change, it just adds a class (center, right or left) and use CSS of flexbox layout to change the position (changing `flex-grow` and `order`). It can be changed per controller or globally in `ActiveScaffold.defaults`:
|
|
76
|
+
|
|
77
|
+
```rb
|
|
78
|
+
conf.config_list.named_views_position = :center
|
|
79
|
+
conf.config_list.named_views_position = :left
|
|
80
|
+
conf.config_list.named_views_position = :right
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Saving to DB
|
|
84
|
+
|
|
85
|
+
The configuration data can be saved on the DB. Define a model to store the list configuration, it must have a config_list
|
|
86
|
+
text column storing the columns list, and config_list_sort serialized text column, a foreign key for the user and a column
|
|
87
|
+
saving the controller name or ID. Also, if using saving named views feature, it must have a column to store the view name,
|
|
88
|
+
and a column to store the slug if global views are enabled. For example:
|
|
51
89
|
|
|
52
90
|
```rb
|
|
53
91
|
# == Schema Information
|
|
@@ -61,6 +99,8 @@ serialized text column. For example:
|
|
|
61
99
|
# updated_at :datetime not null
|
|
62
100
|
# controller_id :string(255)
|
|
63
101
|
# user_id :bigint
|
|
102
|
+
# view_name :string
|
|
103
|
+
# slug :string
|
|
64
104
|
#
|
|
65
105
|
# Indexes
|
|
66
106
|
#
|
|
@@ -73,16 +113,125 @@ class ListConfiguration < ApplicationRecord
|
|
|
73
113
|
end
|
|
74
114
|
```
|
|
75
115
|
|
|
76
|
-
Then in
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
116
|
+
Then in your user model call `has_config_lists 'ModelName'` to define the `has_many` association to the model storing
|
|
117
|
+
the list configuration. The method `has_config_lists` requires a model name, and a hash of options:
|
|
118
|
+
|
|
119
|
+
* association_name: defaults to `list_configurations`
|
|
120
|
+
* association_options: the options for the `has_many` association, for example, `dependent`, `as` if the model has a
|
|
121
|
+
polymorphic association because it's used by different user models, foreign_key, and any other option accepted by
|
|
122
|
+
`has_many`.
|
|
123
|
+
* controller_column: the column storing the controller name or ID.
|
|
124
|
+
* controller_matcher: :controller_id, :controller_name, or a proc. If you want to match the controller column with the
|
|
125
|
+
controller name or controller ID (see below for the difference between controller name and ID).
|
|
126
|
+
* view_name_column: the name for the column storing the view name (see Saving Named Views)
|
|
127
|
+
* slug_column: the name of the column storing the slug for the view (see Global Views)
|
|
128
|
+
|
|
129
|
+
```rb
|
|
130
|
+
class User < ApplicationRecord
|
|
131
|
+
has_config_lists 'ListConfiguration', association_options: {dependent: :delete_all}
|
|
132
|
+
end
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Finally, set the setting `config_list.save_to_user` to `:config_list_for` in the controllers using config_list.
|
|
136
|
+
|
|
137
|
+
```rb
|
|
138
|
+
conf.config_list.save_to_user = :config_list_for
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
It can be changed globally for the app in `ActiveScaffold.defaults` in an initializer, such as:
|
|
142
|
+
|
|
143
|
+
```rb
|
|
144
|
+
ActiveScaffold.defaults do |config|
|
|
145
|
+
config.config_list.save_to_user = :config_list_for
|
|
146
|
+
end
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
There are 2 ways to store the list configuration for a controller. Saving the controller name or the controller ID.
|
|
150
|
+
If the user configuration must be shared on different conditions for embedded controllers, and each parent for nested
|
|
151
|
+
controllers, or save unique configurations. The controller ID argument will be different in embedded controllers,
|
|
152
|
+
nested controllers and regular controllers, as it will use `active_scaffold_session_storage_key`, and the controller
|
|
153
|
+
name will always be the same.
|
|
154
|
+
|
|
155
|
+
## Saving Named Views
|
|
156
|
+
|
|
157
|
+
It's possible to save the list configuration with a name, and change between default view, named views defined in the
|
|
158
|
+
config and user named views. Config list will have a field to set the name, when no name is set, it's saved as a normal
|
|
159
|
+
config list, overriding the default view.
|
|
160
|
+
|
|
161
|
+
When a user named view is selected, configure will open the form with that view's settings. If the name is changed, a
|
|
162
|
+
checkbox will show up that allows renaming the current view. Submitting the form without checking that checkbox will
|
|
163
|
+
create a new named view, and will rename the current view if the checkbox is selected. In this case, it isn't possible
|
|
164
|
+
to override the default view, clearing the view's name won't be allowed.
|
|
165
|
+
|
|
166
|
+
Also, when an user named view is selected, the config list form will have a delete link instead of the reset link.
|
|
167
|
+
|
|
168
|
+

|
|
169
|
+
|
|
170
|
+
Enabling this feature requires enabling saving views to the DB, with `conf.config_list.save_to_user`, as it will use
|
|
171
|
+
the same method to get the named view, and set another method in `conf.config_list.named_views_method`. Both options
|
|
172
|
+
can be set per controller, or globally in `ActiveScaffold.defaults`. Set `named_views_method` to `:config_list_views`.
|
|
173
|
+
|
|
174
|
+
## Global Views
|
|
175
|
+
|
|
176
|
+
It's possible to define global views, so they are available to all users. It's enabled with `conf.config_list.global_views`, and requires to use `save_to_user` and `named_views_method` too, as they work as saved named views.
|
|
177
|
+
|
|
178
|
+
When configuring the list, a global view checkbox will show up when the view name is set, but the global checkbox can be
|
|
179
|
+
changed only if a new view is going to be created (the name of the view is changed, and rename is unchecked). Otherwise,
|
|
180
|
+
global view will be disabled, showing if the view is global or not, but can't be changed until the name is edited.
|
|
181
|
+
|
|
182
|
+
The user association in ListConfiguration must be set as optional, as global views will be saved without the user
|
|
183
|
+
foreign key.
|
|
184
|
+
|
|
185
|
+
```rb
|
|
186
|
+
class ListConfiguration < ApplicationRecord
|
|
187
|
+
belongs_to :user, optional: true
|
|
188
|
+
serialize :config_list_sorting, JSON
|
|
189
|
+
end
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
These views require having a name, then when a name is typed, a global view checkbox will show up. It requires another
|
|
193
|
+
column in the list configuration model, `slug`, as there may be a name collision, and it's hard to prevent, because
|
|
194
|
+
it's weird to forbid creating a global view with a name, because some other user already used it for a private view.
|
|
195
|
+
The slug will be built prefixing the view name with `user-` or `global-` by default.
|
|
196
|
+
|
|
197
|
+
If you want to change how slugs are built globally, you can define a method in ApplicationController, and configure it
|
|
198
|
+
with `conf.config_list.slug_builder` in `ActiveScaffold.defaults`. It's a global setting only, can't be set per controller.
|
|
199
|
+
To customize slug builder in a controller, define `config_list_slug` in the controller. The `config_list_slug` method,
|
|
200
|
+
and the slug builder method, will get the view name and global_view boolean, and must return the slug. It's called when
|
|
201
|
+
saving a view. Changing how slugs are generated can break the default `config_list_for` method if the slug for user views
|
|
202
|
+
doesn't start with `user-`.
|
|
203
|
+
|
|
204
|
+
If you want to override a global view with a private view having the same name, so user don't see 2 options with the same
|
|
205
|
+
name in the views selector, override `config_list_views` in your user model, after calling `has_config_lists`, and get
|
|
206
|
+
uniq views by name:
|
|
207
|
+
|
|
208
|
+
```rb
|
|
209
|
+
def config_list_views(*)
|
|
210
|
+
super.uniq { |(name, _)| name }
|
|
211
|
+
end
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Also, it's possible to override it to change the name of the views, appending an indicator for user views or global views
|
|
215
|
+
when the name is not unique.
|
|
216
|
+
|
|
217
|
+
## Custom model methods
|
|
218
|
+
|
|
219
|
+
It's possible to write custom code in the user model, instead of using `has_config_lists`. Although it's more
|
|
220
|
+
complicated, especially if global views feature is enabled.
|
|
221
|
+
|
|
222
|
+
Define the `has_many` association in the user model, and the method or methods configured in `save_to_user` and
|
|
223
|
+
`named_views_method`.
|
|
224
|
+
|
|
225
|
+
The method configured in `save_to_user` must return the record storing the list configuration for the requested
|
|
226
|
+
controller. The method has 2 arguments, `controller_id` and `controller_name`, so only need to save one of them.
|
|
82
227
|
|
|
83
228
|
```rb
|
|
84
|
-
|
|
85
|
-
|
|
229
|
+
ActiveScaffold.defaults do |config|
|
|
230
|
+
config.config_list.save_to_user = :config_list_for
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
class User < ApplicationRecord
|
|
234
|
+
has_many :list_configurations, dependent: :delete_all
|
|
86
235
|
def config_list_for(controller_id, controller_name)
|
|
87
236
|
# Use controller_id to allow having different columns on different nested or embedded conditions
|
|
88
237
|
list_configurations.where(controller_id: controller_id).first_or_initialize
|
|
@@ -91,3 +240,55 @@ class User < ActiveRecord::Base
|
|
|
91
240
|
end
|
|
92
241
|
end
|
|
93
242
|
```
|
|
243
|
+
|
|
244
|
+
When `named_views_method` is set, the method in `save_to_user` must accept options with double splat (or keyword
|
|
245
|
+
arguments `view_name` and `attributes`), and it's responsible to look for a named view for the user using view_name,
|
|
246
|
+
and return a new model when none is found. Also, it's responsible to set the view_name from opts[:attributes] (or
|
|
247
|
+
`attributes` keyword arg) in the right column. It can use the same model explained before. When selecting the default
|
|
248
|
+
view, and saving it, the keyword arguments will be nil.
|
|
249
|
+
|
|
250
|
+
```rb
|
|
251
|
+
def config_list_for(controller_id, controller_name, **opts)
|
|
252
|
+
list_configurations.where(controller_id: controller_name, view_name: opts[:view_name]).first_or_initialize.tap do |r|
|
|
253
|
+
r.view_name == opts[:attributes][:view_name] if opts[:attributes]
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
The method in `named_views_method` must return an array with the saved named views for the user, and each item will be an array with the label and name in URL parameter (for example, `view_name` and `slug` columns), or just a string to use for both label and name. This method will receive `controller_id` and `controller_name` arguments, just like the method in `save_to_user`.
|
|
259
|
+
|
|
260
|
+
```rb
|
|
261
|
+
ActiveScaffold.defaults do |config|
|
|
262
|
+
config.config_list.named_views_method = :config_list_views
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
class User < ApplicationRecord
|
|
266
|
+
def config_list_views(controller_id, controller_name)
|
|
267
|
+
list_configurations.where(controller_id: controller_name).where.not(view_name: nil).pluck(:view_name)
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
When using global views, the method configured in `save_to_user` will get another keyword argument, `slug`, instead of `view_name`, and must use this one to find the view. The `attributes` keyword argument will have `slug` key and
|
|
273
|
+
`view_name` key. When the provided slug is for a global view, and no view is found, it shouldn't set the user id, but
|
|
274
|
+
when there is no slug, or the slug is for an user view, the user id should be set:
|
|
275
|
+
|
|
276
|
+
```rb
|
|
277
|
+
def config_list_for(controller_id, controller_name, slug: nil, attributes: nil)
|
|
278
|
+
query = list_configurations
|
|
279
|
+
query = ListConfiguration.where(user_id: nil).or(list_configurations) unless slug.to_s.start_with?('user-')
|
|
280
|
+
query.where(controller_id: controller_name, slug: slug).first_or_initialize.tap do |record|
|
|
281
|
+
record.attributes = attributes if attributes
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
And the method configured in `named_views_method` should return the view name and the slug, so user see the view name, and slug is used in the URL:
|
|
287
|
+
|
|
288
|
+
```rb
|
|
289
|
+
def config_list_views(controller_id, controller_name)
|
|
290
|
+
list_configurations.or(ListConfiguration.where(user_id: nil)).where.not(slug: nil).pluck(:view_name, :slug)
|
|
291
|
+
end
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
It's possible to user other column names than `view_name` and `slug`, just ensure the values are set from `attributes[:view_name]` and `attributes[:slug]`, and use the right column names in the queries.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
jQuery(document).ready(function () {
|
|
2
|
+
jQuery(document).on('change', '.config-list-views[data-remote]', function () {
|
|
3
|
+
jQuery(this).closest('form').submit();
|
|
4
|
+
});
|
|
5
|
+
jQuery(document).on('ajax:success', '.config-list-views', function () {
|
|
6
|
+
var form = jQuery(this), link = form.parent().find('.actions [data-action="show_config_list"]'),
|
|
7
|
+
href = new URL(link.attr('href'), window.location.origin);
|
|
8
|
+
href.searchParams.set('config_list_view', form.find('select,input[checked]').val());
|
|
9
|
+
link.attr('href', href.toString());
|
|
10
|
+
});
|
|
11
|
+
jQuery(document).on('input', '.show_config_list-view [name="config_list_view_name"]', function () {
|
|
12
|
+
var field = jQuery(this),
|
|
13
|
+
rename = field.parent().find('.rename-view'),
|
|
14
|
+
global = field.parent().find('.global-view'),
|
|
15
|
+
global_hidden = global.find('input[type=hidden]'),
|
|
16
|
+
global_cb = global.find('input[type=checkbox]'),
|
|
17
|
+
rename_cb = rename.find('input[type=checkbox]');
|
|
18
|
+
if (field.val().trim()) global.show();
|
|
19
|
+
else global.hide();
|
|
20
|
+
if (field.val() !== field.attr('value')) {
|
|
21
|
+
rename.show();
|
|
22
|
+
global_hidden.prop('disabled', rename_cb.length ? !rename_cb.prop('checked') : true);
|
|
23
|
+
global_cb.prop('disabled', rename_cb.length ? rename_cb.prop('checked') : false);
|
|
24
|
+
} else {
|
|
25
|
+
rename.hide();
|
|
26
|
+
rename_cb.prop('checked', false);
|
|
27
|
+
global_hidden.prop('disabled', false);
|
|
28
|
+
global_cb.prop('disabled', true);
|
|
29
|
+
global_cb.prop('checked', global_hidden.val() === '1');
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
jQuery(document).on('change', '.show_config_list-view .rename-view input[type=checkbox]', function () {
|
|
33
|
+
var rename_cb = jQuery(this),
|
|
34
|
+
global = rename_cb.closest('.form-element').find('.global-view'),
|
|
35
|
+
global_hidden = global.find('input[type=hidden]'),
|
|
36
|
+
global_cb = global.find('input[type=checkbox]');
|
|
37
|
+
global_hidden.prop('disabled', !rename_cb.prop('checked'));
|
|
38
|
+
global_cb.prop('disabled', rename_cb.prop('checked'));
|
|
39
|
+
if (global_cb.prop('disabled')) global_cb.prop('checked', global_hidden.val() === '1');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
.active-scaffold-header {
|
|
2
|
+
display: flex;
|
|
3
|
+
justify-content: space-between;
|
|
4
|
+
|
|
5
|
+
> .actions {
|
|
6
|
+
order: 1;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
> h2 {
|
|
10
|
+
flex-grow: 1;
|
|
11
|
+
&:has(~ .config-list-views.left) {
|
|
12
|
+
flex-grow: 0;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
> .config-list-views {
|
|
17
|
+
&.left {
|
|
18
|
+
flex-grow: 1;
|
|
19
|
+
margin-left: 1em;
|
|
20
|
+
}
|
|
21
|
+
&.right {
|
|
22
|
+
order: 2
|
|
23
|
+
}
|
|
24
|
+
display: flex;
|
|
25
|
+
gap: 10px;
|
|
26
|
+
|
|
27
|
+
input[type=radio] {
|
|
28
|
+
margin-right: 5px;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
&:has(.views) {
|
|
32
|
+
position: relative;
|
|
33
|
+
> .selected-view, a {
|
|
34
|
+
display: block;
|
|
35
|
+
padding: 8px 12px;
|
|
36
|
+
white-space: nowrap;
|
|
37
|
+
text-decoration: none;
|
|
38
|
+
color: #333;
|
|
39
|
+
}
|
|
40
|
+
.selected a {
|
|
41
|
+
color: #06c;
|
|
42
|
+
}
|
|
43
|
+
a:hover {
|
|
44
|
+
text-decoration: underline;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
> .views {
|
|
48
|
+
list-style: none;
|
|
49
|
+
padding: 0;
|
|
50
|
+
margin: 0;
|
|
51
|
+
border: 1px solid #ccc;
|
|
52
|
+
display: none;
|
|
53
|
+
background: white;
|
|
54
|
+
}
|
|
55
|
+
&:hover {
|
|
56
|
+
> .views {
|
|
57
|
+
display: block;
|
|
58
|
+
position: absolute;
|
|
59
|
+
top: 0;
|
|
60
|
+
left: 0;
|
|
61
|
+
z-index: 1;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<%
|
|
2
|
+
attributes = {class: "config-list-views #{active_scaffold_config.config_list.named_views_position}"}
|
|
3
|
+
if active_scaffold_config.config_list.named_views_selector == :links
|
|
4
|
+
method = :content_tag
|
|
5
|
+
arg = :div
|
|
6
|
+
else
|
|
7
|
+
method = :form_tag
|
|
8
|
+
arg = params_for(action: :index, config_list_view: nil)
|
|
9
|
+
attributes.merge! remote: true, method: :get
|
|
10
|
+
end
|
|
11
|
+
%>
|
|
12
|
+
<%= send(method, arg, attributes) do %>
|
|
13
|
+
<%= active_scaffold_named_view_selector %>
|
|
14
|
+
<% end %>
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
<% url_options = params_for(action: :
|
|
1
|
+
<% url_options = params_for(action: :config_list) -%>
|
|
2
2
|
<%=
|
|
3
3
|
options = {id: element_form_id(action: :config_list),
|
|
4
4
|
class: "as_form config_list",
|
|
5
5
|
remote: request.xhr?,
|
|
6
|
-
method: :
|
|
6
|
+
method: :post,
|
|
7
7
|
'data-loading' => true}
|
|
8
8
|
form_tag url_options, options %>
|
|
9
9
|
<h4><%= active_scaffold_config.config_list.label -%></h4>
|
|
10
10
|
<%= render 'show_config_list_form_body' %>
|
|
11
11
|
<p class="form-footer">
|
|
12
12
|
<%= submit_tag as_(:config_list), class: "submit" %>
|
|
13
|
-
<%= link_to as_(:reset), url_for(url_options.merge(config_list: '')), class: 'as_cancel', remote: true, data: {refresh: true} %>
|
|
13
|
+
<%= link_to as_(params[:config_list_view].present? ? :delete : :reset), url_for(url_options.merge(config_list: '')), class: 'as_cancel', remote: true, data: {refresh: true, method: :post} %>
|
|
14
14
|
<%= loading_indicator_tag(action: :config_list) %>
|
|
15
15
|
</p>
|
|
16
16
|
</form>
|
|
@@ -3,15 +3,18 @@
|
|
|
3
3
|
<li class="form-element">
|
|
4
4
|
<dl><dt><label><%= as_ :columns %></label></dt>
|
|
5
5
|
<dd>
|
|
6
|
-
<%=
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
6
|
+
<%= active_scaffold_checkbox_list(
|
|
7
|
+
nil,
|
|
8
|
+
config_list_columns,
|
|
9
|
+
config_list_params || config_list_columns.map(&:last),
|
|
10
|
+
{
|
|
11
|
+
name: 'config_list',
|
|
12
|
+
id: "#{element_form_id(action: :config_list)}-columns",
|
|
13
|
+
class: ('sortable-container' unless active_scaffold_config.config_list.draggable)
|
|
14
|
+
},
|
|
15
|
+
ui_options: {draggable_lists: active_scaffold_config.config_list.draggable, item_options: {class: 'sortable'}}
|
|
16
|
+
) %>
|
|
17
|
+
</dd></dl>
|
|
15
18
|
</li>
|
|
16
19
|
<% end %>
|
|
17
20
|
<% if config_list_sorting? %>
|
|
@@ -30,4 +33,26 @@
|
|
|
30
33
|
</ol></dd></dl>
|
|
31
34
|
</li>
|
|
32
35
|
<% end %>
|
|
36
|
+
|
|
37
|
+
<% if config_list_save_named_views? %>
|
|
38
|
+
<li class="form-element">
|
|
39
|
+
<dl><dt><label><%= as_ :view_name %></label></dt>
|
|
40
|
+
<dd>
|
|
41
|
+
<%= text_field_tag "config_list_view_name", @config_list_view_name.to_s, active_scaffold_input_text_options.merge(required: @config_list_view_name.present?) %>
|
|
42
|
+
<% if @config_list_view_name.present? %>
|
|
43
|
+
<label class="rename-view" style="display: none;">
|
|
44
|
+
<input type="checkbox" name="config_list_old_view_name" value="<%= params[:config_list_view] %>" />
|
|
45
|
+
<%= as_ :rename_view, view_name: @config_list_view_name %>
|
|
46
|
+
</label>
|
|
47
|
+
<% end %>
|
|
48
|
+
<% if config_list_global_views? %>
|
|
49
|
+
<%= content_tag :label, class: 'global-view', style: ('display: none;' if @config_list_view_name.blank?) do %>
|
|
50
|
+
<%= hidden_field_tag 'global_view', @global_view ? '1' : '' %>
|
|
51
|
+
<%= check_box_tag 'global_view', '1', @global_view, id: nil, disabled: true %>
|
|
52
|
+
<%= as_ :global_view %>
|
|
53
|
+
<% end %>
|
|
54
|
+
<% end %>
|
|
55
|
+
</dd></dl>
|
|
56
|
+
</li>
|
|
57
|
+
<% end %>
|
|
33
58
|
</ol>
|
data/config/locales/de.yml
CHANGED
data/config/locales/en.yml
CHANGED
data/config/locales/es.yml
CHANGED
data/config/locales/fr.yml
CHANGED
data/config/locales/hu.yml
CHANGED
data/config/locales/ja.yml
CHANGED
data/config/locales/ru.yml
CHANGED
|
@@ -2,22 +2,58 @@ module ActiveScaffold::Actions
|
|
|
2
2
|
module ConfigList
|
|
3
3
|
|
|
4
4
|
def self.included(base)
|
|
5
|
-
base.before_action :
|
|
6
|
-
base.
|
|
5
|
+
base.before_action :set_default_sorting, :change_view, only: [:index]
|
|
6
|
+
base.before_action :config_list_authorized_filter, only: [:show_config_list, :config_list]
|
|
7
|
+
base.helper_method :config_list_params, :config_list_sorting, :config_list_named_views
|
|
7
8
|
end
|
|
8
9
|
|
|
9
10
|
def show_config_list
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
type.js do
|
|
15
|
-
render(partial: 'show_config_list_form', layout: false)
|
|
16
|
-
end
|
|
11
|
+
config_list_record
|
|
12
|
+
@config_list_view_name = find_view_name(params[:config_list_view])
|
|
13
|
+
if @config_list_view_name.present?
|
|
14
|
+
@global_view = config_list_slug(@config_list_view_name, true) == params[:config_list_view]
|
|
17
15
|
end
|
|
16
|
+
respond_to_action(:show_config_list, config_list_formats)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def config_list
|
|
20
|
+
do_config_list
|
|
21
|
+
respond_to_action(:config_list, config_list_formats)
|
|
18
22
|
end
|
|
19
23
|
|
|
20
24
|
protected
|
|
25
|
+
|
|
26
|
+
def change_view
|
|
27
|
+
active_scaffold_config.list.refresh_with_header = true if params[:config_list_view]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def config_list_formats
|
|
31
|
+
[:html, :js]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def show_config_list_respond_to_html
|
|
35
|
+
render(action: 'show_config_list_form', layout: true)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def show_config_list_respond_to_js
|
|
39
|
+
render(partial: 'show_config_list_form', layout: false)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def config_list_respond_to_html
|
|
43
|
+
redirect_to action: :index, config_list_view: params[:config_list_view_name]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def do_refresh_list
|
|
47
|
+
set_default_sorting
|
|
48
|
+
super
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def config_list_respond_to_js
|
|
52
|
+
do_refresh_list
|
|
53
|
+
active_scaffold_config.list.refresh_with_header = true
|
|
54
|
+
list_url = params_for(action: :index, config_list_view: params[:config_list_view].presence)
|
|
55
|
+
render partial: 'refresh_list', locals: {history_url: url_for(list_url)}, formats: [:js]
|
|
56
|
+
end
|
|
21
57
|
|
|
22
58
|
def set_default_sorting
|
|
23
59
|
if (params['sort_direction'] && params['sort_direction'] == 'reset') || (active_scaffold_config.list.user['sort'].blank? && params['sort'].blank?)
|
|
@@ -25,15 +61,13 @@ module ActiveScaffold::Actions
|
|
|
25
61
|
end
|
|
26
62
|
end
|
|
27
63
|
|
|
28
|
-
def
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
save_config_list_params(config_list, config_list_sorting_params)
|
|
36
|
-
end
|
|
64
|
+
def do_config_list
|
|
65
|
+
config_list = params.delete :config_list
|
|
66
|
+
case config_list
|
|
67
|
+
when String
|
|
68
|
+
delete_config_list_params(params.delete(:config_list_view).presence)
|
|
69
|
+
when Array
|
|
70
|
+
save_config_list_params(config_list, config_list_sorting_params, params.delete(:config_list_view_name).presence, params.delete(:config_list_old_view_name))
|
|
37
71
|
end
|
|
38
72
|
end
|
|
39
73
|
|
|
@@ -70,9 +104,13 @@ module ActiveScaffold::Actions
|
|
|
70
104
|
@active_scaffold_current_user ||= send(self.class.active_scaffold_config.class.security.current_user_method)
|
|
71
105
|
end
|
|
72
106
|
|
|
73
|
-
def delete_config_list_params
|
|
74
|
-
|
|
75
|
-
|
|
107
|
+
def delete_config_list_params(view_name)
|
|
108
|
+
record = config_list_record(view_name)
|
|
109
|
+
if record
|
|
110
|
+
if record.destroy && view_name.present?
|
|
111
|
+
config_list_record(reload: true)
|
|
112
|
+
return
|
|
113
|
+
end
|
|
76
114
|
else
|
|
77
115
|
config_list_session_storage['config_list'] = nil
|
|
78
116
|
config_list_session_storage['config_list_sorting'] = nil
|
|
@@ -82,12 +120,17 @@ module ActiveScaffold::Actions
|
|
|
82
120
|
@config_list_sorting = nil
|
|
83
121
|
end
|
|
84
122
|
|
|
85
|
-
def save_config_list_params(config_list, config_list_sorting)
|
|
86
|
-
if
|
|
87
|
-
|
|
88
|
-
|
|
123
|
+
def save_config_list_params(config_list, config_list_sorting, view_name, old_view_name)
|
|
124
|
+
if view_name.present?
|
|
125
|
+
assign = {view_name: view_name}
|
|
126
|
+
assign[:slug] = config_list_slug(view_name, params.delete(:global_view).present?) if active_scaffold_config.config_list.global_views
|
|
127
|
+
end
|
|
128
|
+
record = config_list_record(old_view_name || assign&.dig(:slug) || view_name, assign: assign)
|
|
129
|
+
|
|
130
|
+
if record
|
|
131
|
+
record.config_list = config_list.select(&:present?).join(',')
|
|
89
132
|
record.config_list_sorting = config_list_sorting if record.respond_to? :config_list_sorting
|
|
90
|
-
record.save
|
|
133
|
+
params[:config_list_view] = assign&.dig(:slug) || view_name if record.save
|
|
91
134
|
else
|
|
92
135
|
config_list_session_storage['config_list'] = config_list.map(&:to_sym)
|
|
93
136
|
config_list_session_storage['config_list_sorting'] = config_list_sorting
|
|
@@ -111,14 +154,52 @@ module ActiveScaffold::Actions
|
|
|
111
154
|
end
|
|
112
155
|
end
|
|
113
156
|
|
|
114
|
-
def config_list_record
|
|
115
|
-
return @config_list_record if defined?
|
|
157
|
+
def config_list_record(view_name = params[:config_list_view], reload: false, assign: nil)
|
|
158
|
+
return @config_list_record if !reload && defined?(@config_list_record)
|
|
116
159
|
@config_list_record =
|
|
117
160
|
if active_scaffold_config.config_list.save_to_user && active_scaffold_current_user
|
|
118
|
-
|
|
161
|
+
if view_name.present?
|
|
162
|
+
options = {attributes: assign}
|
|
163
|
+
options[active_scaffold_config.config_list.global_views ? :slug : :view_name] = view_name
|
|
164
|
+
end
|
|
165
|
+
active_scaffold_current_user.send(
|
|
166
|
+
active_scaffold_config.config_list.save_to_user,
|
|
167
|
+
config_list_session_storage_key,
|
|
168
|
+
config_list_controller_name,
|
|
169
|
+
**options
|
|
170
|
+
)
|
|
119
171
|
end
|
|
120
172
|
end
|
|
121
173
|
|
|
174
|
+
def find_view_name(slug)
|
|
175
|
+
return unless slug.present?
|
|
176
|
+
|
|
177
|
+
config_list_named_views.find do |view_name, view_slug|
|
|
178
|
+
break view_name if slug == (view_slug || view_name)
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def config_list_slug(view_name, global_view)
|
|
183
|
+
return view_name unless active_scaffold_config.config_list.global_views
|
|
184
|
+
|
|
185
|
+
if ActiveScaffold::Config::ConfigList.slug_builder
|
|
186
|
+
send(ActiveScaffold::Config::ConfigList.slug_builder, view_name, global_view)
|
|
187
|
+
elsif global_view
|
|
188
|
+
"global-#{view_name}"
|
|
189
|
+
else
|
|
190
|
+
"user-#{view_name}"
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def config_list_named_views
|
|
195
|
+
@config_list_named_views ||=
|
|
196
|
+
active_scaffold_current_user.send(
|
|
197
|
+
active_scaffold_config.config_list.named_views_method,
|
|
198
|
+
config_list_session_storage_key,
|
|
199
|
+
config_list_controller_name
|
|
200
|
+
).compact
|
|
201
|
+
end
|
|
202
|
+
|
|
122
203
|
def config_list_params
|
|
123
204
|
unless defined? @config_list_params
|
|
124
205
|
@config_list_params =
|
|
@@ -139,7 +220,9 @@ module ActiveScaffold::Actions
|
|
|
139
220
|
def config_list_sorting
|
|
140
221
|
unless defined? @config_list_sorting
|
|
141
222
|
@config_list_sorting =
|
|
142
|
-
if
|
|
223
|
+
if named_view
|
|
224
|
+
named_view.sorting
|
|
225
|
+
elsif config_list_record
|
|
143
226
|
config_list_record.config_list_sorting if config_list_record.respond_to? :config_list_sorting
|
|
144
227
|
else
|
|
145
228
|
config_list_session_storage['config_list_sorting']
|
|
@@ -149,7 +232,7 @@ module ActiveScaffold::Actions
|
|
|
149
232
|
end
|
|
150
233
|
|
|
151
234
|
def list_columns
|
|
152
|
-
@list_columns ||= begin
|
|
235
|
+
@list_columns ||= named_view_columns || begin
|
|
153
236
|
columns = super
|
|
154
237
|
if config_list_params.present?
|
|
155
238
|
config_list = config_list_params.each.with_index.to_h
|
|
@@ -164,7 +247,24 @@ module ActiveScaffold::Actions
|
|
|
164
247
|
# The default security delegates to ActiveRecordPermissions.
|
|
165
248
|
# You may override the method to customize.
|
|
166
249
|
def config_list_authorized?
|
|
167
|
-
authorized_for?(action: :read)
|
|
250
|
+
named_view.nil? && authorized_for?(action: :read)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def named_view
|
|
254
|
+
return unless params[:config_list_view].present?
|
|
255
|
+
|
|
256
|
+
@named_view ||= active_scaffold_config.config_list.named_views.find { |v| v.name == params[:config_list_view] }
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def named_view_columns
|
|
260
|
+
return unless named_view
|
|
261
|
+
|
|
262
|
+
raise ActiveScaffold::ActionNotAllowed if named_view.security_method && !send(named_view.security_method)
|
|
263
|
+
named_view.columns.visible_columns(flatten: true)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def config_list_authorized_filter
|
|
267
|
+
raise ActiveScaffold::ActionNotAllowed unless config_list_authorized?
|
|
168
268
|
end
|
|
169
269
|
end
|
|
170
270
|
end
|
|
@@ -5,7 +5,12 @@ module ActiveScaffold::Config
|
|
|
5
5
|
super
|
|
6
6
|
@link = self.class.link.clone unless self.class.link.nil?
|
|
7
7
|
@save_to_user = self.class.save_to_user
|
|
8
|
+
@named_views_method = self.class.named_views_method
|
|
8
9
|
@draggable = self.class.draggable
|
|
10
|
+
@named_views_position = self.class.named_views_position
|
|
11
|
+
@named_views_selector = self.class.named_views_selector
|
|
12
|
+
@global_views = self.class.global_views
|
|
13
|
+
@named_views = []
|
|
9
14
|
end
|
|
10
15
|
|
|
11
16
|
# global level configuration
|
|
@@ -26,6 +31,29 @@ module ActiveScaffold::Config
|
|
|
26
31
|
# configures the method in user model to save list configuration for every controller
|
|
27
32
|
cattr_accessor :save_to_user
|
|
28
33
|
|
|
34
|
+
# configures the method in user model to return the named views saved for the user,
|
|
35
|
+
# setting it enables saving the views, but requires to set a method in save_to_user
|
|
36
|
+
cattr_accessor :named_views_method
|
|
37
|
+
|
|
38
|
+
# the position to place the named views selector, :left (next to the title),
|
|
39
|
+
# :right or :center (default, before the actions and filters)
|
|
40
|
+
cattr_accessor :named_views_position
|
|
41
|
+
@@named_views_position = :center
|
|
42
|
+
|
|
43
|
+
# the type of selector for named views, :links, :select or :radio
|
|
44
|
+
cattr_accessor :named_views_selector
|
|
45
|
+
@@named_views_selector = :links
|
|
46
|
+
|
|
47
|
+
# if saving global views is allowed, or named views are always saved for the current user only
|
|
48
|
+
cattr_accessor :global_views
|
|
49
|
+
|
|
50
|
+
# Configures the method to convert a view name to slug, used only when global views are enabled
|
|
51
|
+
# Slugs are needed when global views are enabled, to ensure the name in the params is unique
|
|
52
|
+
# although a user view has the same name as a global view
|
|
53
|
+
# By default is generated with the prefixes global- and user-, but a different method can be setup here
|
|
54
|
+
# so it can be defined in ApplicationController
|
|
55
|
+
cattr_accessor :slug_builder
|
|
56
|
+
|
|
29
57
|
# enable draggable lists to select displayed columns (enabled by default)
|
|
30
58
|
cattr_accessor :draggable
|
|
31
59
|
self.draggable = true
|
|
@@ -44,6 +72,23 @@ module ActiveScaffold::Config
|
|
|
44
72
|
# configures the method in user model to save list configuration for every controller
|
|
45
73
|
attr_accessor :save_to_user
|
|
46
74
|
|
|
75
|
+
# configures the method in user model to return the named views saved for the user,
|
|
76
|
+
# setting it enables saving the views, but requires to set a method in save_to_user
|
|
77
|
+
attr_accessor :named_views_method
|
|
78
|
+
|
|
79
|
+
# the position to place the named views selector, :left (next to the title),
|
|
80
|
+
# :right or :center (default, before the actions and filters)
|
|
81
|
+
attr_accessor :named_views_position
|
|
82
|
+
|
|
83
|
+
# the type of selector for named views, :links, :select or :radio
|
|
84
|
+
attr_accessor :named_views_selector
|
|
85
|
+
|
|
86
|
+
# if saving global views is allowed, or named views are always saved for the current user only
|
|
87
|
+
attr_accessor :global_views
|
|
88
|
+
|
|
89
|
+
# generic named views
|
|
90
|
+
attr_reader :named_views
|
|
91
|
+
|
|
47
92
|
# enable draggable lists to select displayed columns
|
|
48
93
|
attr_accessor :draggable
|
|
49
94
|
|
|
@@ -52,12 +97,24 @@ module ActiveScaffold::Config
|
|
|
52
97
|
columns.exclude :created_on, :created_at, :updated_on, :updated_at, :as_marked
|
|
53
98
|
columns.exclude *@core.columns.select{|c| c.association.try(:polymorphic?)}.map(&:name)
|
|
54
99
|
end
|
|
55
|
-
|
|
100
|
+
|
|
56
101
|
# the ActionLink for this action
|
|
57
102
|
attr_accessor :link
|
|
58
103
|
|
|
104
|
+
def add_view(name, columns = nil, &)
|
|
105
|
+
raise ArgumentError, "view '#{name}' already exists" if @named_views.find { |v| v.name == name }
|
|
106
|
+
view = ::ActiveScaffold::DataStructures::NamedView.new(name, self)
|
|
107
|
+
view.columns = columns if columns
|
|
108
|
+
yield view if block_given?
|
|
109
|
+
raise ArgumentError, "no columns defined for view '#{name}'" if view.columns.empty?
|
|
110
|
+
|
|
111
|
+
@named_views << view
|
|
112
|
+
view
|
|
113
|
+
end
|
|
114
|
+
|
|
59
115
|
UserSettings.class_eval do
|
|
60
|
-
user_attr :default_columns, :save_to_user, :draggable
|
|
116
|
+
user_attr :default_columns, :save_to_user, :named_views_method, :draggable,
|
|
117
|
+
:named_views_position, :named_views_selector, :global_views
|
|
61
118
|
|
|
62
119
|
def label
|
|
63
120
|
@conf.label(core: core)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveScaffold::DataStructures
|
|
4
|
+
class NamedView
|
|
5
|
+
# the internal name of the view
|
|
6
|
+
attr_reader :name
|
|
7
|
+
|
|
8
|
+
# the label displayed to the user, if it's a symbol will be translated with as_
|
|
9
|
+
attr_writer :label
|
|
10
|
+
|
|
11
|
+
# the method used to check permissions, if returns true, the list view is allowed
|
|
12
|
+
attr_accessor :security_method
|
|
13
|
+
|
|
14
|
+
# the columns in the named view
|
|
15
|
+
attr_reader :columns
|
|
16
|
+
|
|
17
|
+
# default sorting used with this view instead of default
|
|
18
|
+
attr_accessor :sorting
|
|
19
|
+
|
|
20
|
+
def initialize(name, action)
|
|
21
|
+
@name = name.to_s
|
|
22
|
+
@action = action
|
|
23
|
+
@label = name
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# the label displayed to the user
|
|
27
|
+
def label
|
|
28
|
+
case @label
|
|
29
|
+
when Symbol
|
|
30
|
+
ActiveScaffold::Registry.cache(:translations, @label) { as_(@label) }
|
|
31
|
+
else
|
|
32
|
+
@label
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def columns=(columns)
|
|
37
|
+
@columns = ActionColumns.new(columns).tap { |cols| cols.action = @action }
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -9,6 +9,22 @@ module ActiveScaffold
|
|
|
9
9
|
true
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
+
def config_list_save_named_views?
|
|
13
|
+
active_scaffold_config.config_list.named_views_method.present?
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def config_list_named_views?
|
|
17
|
+
return unless active_scaffold_config.actions.include? :config_list
|
|
18
|
+
|
|
19
|
+
config_list_save_named_views? || active_scaffold_config.config_list.named_views.present?
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def config_list_global_views?
|
|
23
|
+
return unless active_scaffold_config.actions.include? :config_list
|
|
24
|
+
|
|
25
|
+
config_list_save_named_views? && active_scaffold_config.config_list.global_views
|
|
26
|
+
end
|
|
27
|
+
|
|
12
28
|
def config_list_columns
|
|
13
29
|
columns =
|
|
14
30
|
if config_list_params.is_a?(Array)
|
|
@@ -19,6 +35,57 @@ module ActiveScaffold
|
|
|
19
35
|
end
|
|
20
36
|
columns.map { |c| [column_heading_label(c), c.name] }
|
|
21
37
|
end
|
|
38
|
+
|
|
39
|
+
def user_named_views
|
|
40
|
+
return [] unless config_list_save_named_views?
|
|
41
|
+
config_list_named_views
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def named_views_from_config
|
|
45
|
+
active_scaffold_config.config_list.named_views.filter_map do |view|
|
|
46
|
+
next if view.security_method && !controller.send(view.security_method)
|
|
47
|
+
[view.label, view.name]
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def active_scaffold_named_view_selector
|
|
52
|
+
named_views = user_named_views + named_views_from_config
|
|
53
|
+
return unless named_views.any?
|
|
54
|
+
|
|
55
|
+
named_views.unshift [as_(:default_view), '']
|
|
56
|
+
html = config_list_view_options(named_views, params[:config_list_view].to_s)
|
|
57
|
+
if active_scaffold_config.config_list.named_views_selector == :select
|
|
58
|
+
html = select_tag('config_list_view', html, id: nil)
|
|
59
|
+
end
|
|
60
|
+
html
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def config_list_view_options(named_views, selected)
|
|
64
|
+
case active_scaffold_config.config_list.named_views_selector
|
|
65
|
+
when :links
|
|
66
|
+
url = url_for(params_for(action: :index, config_list_view: '--VIEW--'))
|
|
67
|
+
view = nil
|
|
68
|
+
links = named_views.map do |(label, name)|
|
|
69
|
+
name ||= label
|
|
70
|
+
link = link_to(label, url.sub('--VIEW--', ERB::Util.unwrapped_html_escape(name)), remote: true)
|
|
71
|
+
view = label if name == selected
|
|
72
|
+
content_tag :li, link, class: ('selected' if name == selected)
|
|
73
|
+
end
|
|
74
|
+
content_tag(:div, view || as_(:default_view), class: 'selected-view') + content_tag(:ul, safe_join(links), class: 'views')
|
|
75
|
+
|
|
76
|
+
when :select
|
|
77
|
+
options_for_select(named_views, selected)
|
|
78
|
+
|
|
79
|
+
when :radio
|
|
80
|
+
buttons = named_views.map do |(label, name)|
|
|
81
|
+
name ||= label
|
|
82
|
+
content_tag(:label) do
|
|
83
|
+
radio_button_tag('config_list_view', name, name == selected, id: nil) + label
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
safe_join(buttons)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
22
89
|
end
|
|
23
90
|
end
|
|
24
91
|
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveScaffoldConfigList
|
|
4
|
+
# app/models/concerns/config_listable.rb
|
|
5
|
+
module ConfigListable
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
class_methods do
|
|
9
|
+
def has_config_lists(model_name,
|
|
10
|
+
association_name: :list_configurations,
|
|
11
|
+
association_options: {},
|
|
12
|
+
view_name_column: :view_name,
|
|
13
|
+
slug_column: :slug,
|
|
14
|
+
controller_column: :controller_id,
|
|
15
|
+
controller_matcher: :controller_name)
|
|
16
|
+
# Define the association
|
|
17
|
+
class_eval do
|
|
18
|
+
has_many association_name, class_name: model_name, **association_options
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
model_class = model_name.to_s.classify.constantize
|
|
22
|
+
view_name_column = nil unless model_class.column_names.include?(view_name_column.to_s)
|
|
23
|
+
slug_column = nil unless model_class.column_names.include?(slug_column.to_s)
|
|
24
|
+
|
|
25
|
+
self.config_list_settings = {
|
|
26
|
+
association_name: association_name,
|
|
27
|
+
model_name: model_name,
|
|
28
|
+
view_name_column: view_name_column,
|
|
29
|
+
slug_column: slug_column,
|
|
30
|
+
controller_column: controller_column,
|
|
31
|
+
model_class: model_class,
|
|
32
|
+
foreign_key: reflect_on_association(association_name).foreign_key,
|
|
33
|
+
controller_matcher: controller_matcher
|
|
34
|
+
}.freeze
|
|
35
|
+
|
|
36
|
+
# Include instance methods
|
|
37
|
+
include ConfigListable::InstanceMethods unless included_modules.include?(ConfigListable::InstanceMethods)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
included do
|
|
42
|
+
class_attribute :config_list_settings, instance_writer: false, default: nil
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
module InstanceMethods
|
|
46
|
+
def config_list_for(controller_id, controller_name, view_name: nil, slug: nil, attributes: nil)
|
|
47
|
+
settings = self.class.config_list_settings
|
|
48
|
+
query = list_configurations_query(controller_id, controller_name, user_views: slug.nil? || slug.to_s.start_with?('user-'))
|
|
49
|
+
|
|
50
|
+
if slug
|
|
51
|
+
query = query.where(settings[:slug_column] => slug)
|
|
52
|
+
elsif view_name
|
|
53
|
+
query = query.where(settings[:view_name_column] => view_name)
|
|
54
|
+
else
|
|
55
|
+
column = settings[:slug_column] || settings[:view_name_column]
|
|
56
|
+
query = query.where(column => nil) if column
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
query.first_or_initialize.tap do |record|
|
|
60
|
+
record.attributes = map_attribute_names(attributes, settings) if attributes
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def config_list_views(controller_id, controller_name)
|
|
65
|
+
settings = self.class.config_list_settings
|
|
66
|
+
list_configurations_query(controller_id, controller_name)
|
|
67
|
+
.where.not(settings[:view_name_column] => nil)
|
|
68
|
+
.pluck([settings[:view_name_column], settings[:slug_column]].compact)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def determine_controller_value(controller_id, controller_name, settings)
|
|
74
|
+
matcher = settings[:controller_matcher]
|
|
75
|
+
|
|
76
|
+
case matcher
|
|
77
|
+
when :controller_id
|
|
78
|
+
{settings[:controller_column] => controller_id}
|
|
79
|
+
else
|
|
80
|
+
# Default to controller_id if matcher is a callable
|
|
81
|
+
if matcher.respond_to?(:call)
|
|
82
|
+
matcher.call(controller_id, controller_name)
|
|
83
|
+
else
|
|
84
|
+
# Fallback to controller_name
|
|
85
|
+
{settings[:controller_column] => controller_name}
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def list_configurations_query(controller_id, controller_name, user_views: false)
|
|
91
|
+
settings = self.class.config_list_settings
|
|
92
|
+
query = send(settings[:association_name])
|
|
93
|
+
# Add the OR condition if user_views is false
|
|
94
|
+
query = settings[:model_class].where(settings[:foreign_key] => nil).or(query) unless user_views
|
|
95
|
+
query.where(determine_controller_value(controller_id, controller_name, settings))
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def map_attribute_names(attributes, settings)
|
|
99
|
+
return attributes unless attributes.is_a?(Hash)
|
|
100
|
+
|
|
101
|
+
attributes.symbolize_keys.transform_keys do |key|
|
|
102
|
+
case key
|
|
103
|
+
when :view_name then settings[:view_name_column]
|
|
104
|
+
when :slug then settings[:slug_column]
|
|
105
|
+
else
|
|
106
|
+
key
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
require 'active_scaffold_config_list/config_listable'
|
|
1
2
|
module ActiveScaffoldConfigList
|
|
2
3
|
class Engine < ::Rails::Engine
|
|
3
4
|
initializer 'active_scaffold_config_list.routes' do
|
|
4
5
|
ActiveSupport.on_load :active_scaffold_routing do
|
|
5
6
|
self::ACTIVE_SCAFFOLD_CORE_ROUTING[:collection][:show_config_list] = :get
|
|
7
|
+
self::ACTIVE_SCAFFOLD_CORE_ROUTING[:collection][:config_list] = :post
|
|
6
8
|
end
|
|
7
9
|
end
|
|
8
10
|
|
|
@@ -11,5 +13,11 @@ module ActiveScaffoldConfigList
|
|
|
11
13
|
ActionView::Base.send :include, ActiveScaffold::Helpers::ConfigListHelpers
|
|
12
14
|
end
|
|
13
15
|
end
|
|
16
|
+
|
|
17
|
+
initializer 'active_scaffold_config_list.active_record' do |app|
|
|
18
|
+
ActiveSupport.on_load :active_record do
|
|
19
|
+
include ActiveScaffoldConfigList::ConfigListable
|
|
20
|
+
end
|
|
21
|
+
end
|
|
14
22
|
end
|
|
15
23
|
end
|
|
@@ -10,8 +10,14 @@ module ActiveScaffold
|
|
|
10
10
|
ActiveScaffold.autoload_subdir('config', self, File.dirname(__FILE__))
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
+
module DataStructures
|
|
14
|
+
ActiveScaffold.autoload_subdir('data_structures', self, File.dirname(__FILE__))
|
|
15
|
+
end
|
|
16
|
+
|
|
13
17
|
module Helpers
|
|
14
18
|
ActiveScaffold.autoload_subdir('helpers', self, File.dirname(__FILE__))
|
|
15
19
|
end
|
|
16
20
|
end
|
|
17
|
-
ActiveSupport.run_load_hooks(:active_scaffold_config_list)
|
|
21
|
+
ActiveSupport.run_load_hooks(:active_scaffold_config_list)
|
|
22
|
+
ActiveScaffold.stylesheets << 'active_scaffold_config_list'
|
|
23
|
+
ActiveScaffold.javascripts << 'active_scaffold_config_list'
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: active_scaffold_config_list
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 4.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sergio Cambra
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-02-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: active_scaffold
|
|
@@ -16,14 +16,14 @@ dependencies:
|
|
|
16
16
|
requirements:
|
|
17
17
|
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version:
|
|
19
|
+
version: 4.2.0
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
24
|
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version:
|
|
26
|
+
version: 4.2.0
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: active_scaffold_sortable
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -47,6 +47,10 @@ extra_rdoc_files:
|
|
|
47
47
|
files:
|
|
48
48
|
- LICENSE.txt
|
|
49
49
|
- README.md
|
|
50
|
+
- app/assets/javascripts/active_scaffold_config_list.js
|
|
51
|
+
- app/assets/stylesheets/active_scaffold_config_list.scss
|
|
52
|
+
- app/views/active_scaffold_overrides/_list_header.html.erb
|
|
53
|
+
- app/views/active_scaffold_overrides/_named_views.html.erb
|
|
50
54
|
- app/views/active_scaffold_overrides/_show_config_list_form.html.erb
|
|
51
55
|
- app/views/active_scaffold_overrides/_show_config_list_form_body.html.erb
|
|
52
56
|
- app/views/active_scaffold_overrides/show_config_list_form.html.erb
|
|
@@ -59,8 +63,10 @@ files:
|
|
|
59
63
|
- config/locales/ru.yml
|
|
60
64
|
- lib/active_scaffold/actions/config_list.rb
|
|
61
65
|
- lib/active_scaffold/config/config_list.rb
|
|
66
|
+
- lib/active_scaffold/data_structures/named_view.rb
|
|
62
67
|
- lib/active_scaffold/helpers/config_list_helpers.rb
|
|
63
68
|
- lib/active_scaffold_config_list.rb
|
|
69
|
+
- lib/active_scaffold_config_list/config_listable.rb
|
|
64
70
|
- lib/active_scaffold_config_list/engine.rb
|
|
65
71
|
- lib/active_scaffold_config_list/version.rb
|
|
66
72
|
homepage: http://activescaffold.com
|