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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 50364f3882bef87059e8b9355d09cd218d237f6a5a4932fd647117c655d64e11
4
- data.tar.gz: e7bcab07d7f032be6174a2cd3ea8295cd1f6ec6a1742f0ad567bc1b4ec0cc950
3
+ metadata.gz: 2065069db537dffc9b14bbdb46247225138c88b5e521998810333f7d602978ba
4
+ data.tar.gz: 19471cbf086ad1f9ab84f5acf6bcbd3d1c336496f84a13838985f1ae26b72722
5
5
  SHA512:
6
- metadata.gz: 114309cc4df41633688338ad1495ab848e056b1b8698a9f2046ed4bd76e54bb20a61850affec8c4cc46453e23c0d98a7119eac463a6c9d8e46b2847687be0f65
7
- data.tar.gz: 94b48977865381533466595356b71cfe4c16b5aff4c4ae499a0d22c2a6480c57d6a206b66c3dcb2b61ad11d410f80c7e2aacc3928eec741a6e1b2e575ee3d523
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 on the session. It can be saved on the DB defining a method on the user model
34
- (the one returned by current_user method) to return a record for current controller, or empty record if user has no
35
- list configuration, and setting config_list to use that method.
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.save_to_user = :config_list_for
41
+ conf.config_list.add_view :simple, [:number, :status]
39
42
  ```
40
43
 
41
- It can be changed globally for the app in `ActiveScaffold.defaults` in an initializer, such as:
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
- ActiveScaffold.defaults do |config|
45
- config.config_list.save_to_user
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
- The model storing list configuration must have a config_list text column storing the columns list, and config_list_sort
50
- serialized text column. For example:
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 the User model, define the association, and the method returning the config list for the requested controller.
77
- The method has 2 arguments, `controller_id` and `controller_name`, so only need to save one of them, depending if user
78
- configuration must be shared on different conditions for embedded controllers, and each parent for nested controllers,
79
- or save unique configurations. The `controller_id` argument will be different in embedded controllers, nested controllers
80
- and regular controllers, as it will use `active_scaffold_session_storage_key`, and the controller_name will be the same
81
- always.
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
+ ![screenshot](./images/config-named-view.png)
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
- class User < ActiveRecord::Base
85
- has_many :list_configurations
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,2 @@
1
+ <%= render :super %>
2
+ <%= render 'named_views' if config_list_named_views? %>
@@ -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: :index, escape: false, config_list: nil) -%>
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: :get,
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
- <%= active_scaffold_check_all_buttons(nil, {}, ui_options: {}) if respond_to? :active_scaffold_check_all_buttons %>
7
- <ul class="<%= active_scaffold_config.config_list.draggable ? 'checkbox-list draggable-lists' : 'sortable-container' %>" id="<%= element_form_id(action: :config_list) %>-columns">
8
- <% config_list_columns.each do |label, column| %>
9
- <li class="sortable">
10
- <%= check_box_tag 'config_list[]', column.to_s, (config_list_params.nil? ? true : config_list_params.include?(column)), {id: nil}%>
11
- <label><%= label %></label>
12
- </li>
13
- <% end %>
14
- </ul></dd></dl>
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>
@@ -5,4 +5,8 @@ de:
5
5
  config_list_model: "Konfiguriere Spalten für %{model}"
6
6
  columns: Columns
7
7
  default_sorting: Default Sorting
8
+ default_view: Default
8
9
  desc: desc
10
+ global_view: Global view
11
+ rename_view: 'Rename the view "%{view_name}"'
12
+ view_name: View Name
@@ -5,4 +5,8 @@ en:
5
5
  config_list_model: "Configure Columns for %{model}"
6
6
  columns: Columns
7
7
  default_sorting: Default Sorting
8
+ default_view: Default
8
9
  desc: desc
10
+ global_view: Global view
11
+ rename_view: 'Rename the view "%{view_name}"'
12
+ view_name: View Name
@@ -5,4 +5,8 @@ es:
5
5
  config_list_model: "Configurar columnas de %{model}"
6
6
  columns: Columnas
7
7
  default_sorting: Orden por defecto
8
+ default_view: Por defecto
8
9
  desc: desc
10
+ global_view: Vista global
11
+ rename_view: 'Renombrar la vista "%{view_name}"'
12
+ view_name: Nombre de vista
@@ -5,4 +5,8 @@ fr:
5
5
  config_list_model: "Configurer les colonnes pour %{model}"
6
6
  columns: Columns
7
7
  default_sorting: Default Sorting
8
+ default_view: Default
8
9
  desc: desc
10
+ global_view: Global view
11
+ rename_view: 'Rename the view "%{view_name}"'
12
+ view_name: View Name
@@ -5,4 +5,8 @@ hu:
5
5
  config_list_model: "Configure Columns for %{model}"
6
6
  columns: Columns
7
7
  default_sorting: Default Sorting
8
+ default_view: Default
8
9
  desc: desc
10
+ global_view: Global view
11
+ rename_view: 'Rename the view "%{view_name}"'
12
+ view_name: View Name
@@ -5,4 +5,8 @@ ja:
5
5
  config_list_model: "Configure Columns for %{model}"
6
6
  columns: Columns
7
7
  default_sorting: Default Sorting
8
+ default_view: Default
8
9
  desc: desc
10
+ global_view: Global view
11
+ rename_view: 'Rename the view "%{view_name}"'
12
+ view_name: View Name
@@ -5,4 +5,8 @@ ru:
5
5
  config_list_model: "%{model}: настройки списка"
6
6
  columns: Columns
7
7
  default_sorting: Default Sorting
8
+ default_view: Default
8
9
  desc: desc
10
+ global_view: Global view
11
+ rename_view: 'Rename the view "%{view_name}"'
12
+ view_name: View Name
@@ -2,22 +2,58 @@ module ActiveScaffold::Actions
2
2
  module ConfigList
3
3
 
4
4
  def self.included(base)
5
- base.before_action :store_config_list_params, :set_default_sorting, only: [:index]
6
- base.helper_method :config_list_params, :config_list_sorting
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
- respond_to do |type|
11
- type.html do
12
- render(action: 'show_config_list_form', layout: true)
13
- end
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 store_config_list_params
29
- if params[:config_list]
30
- config_list = params.delete :config_list
31
- case config_list
32
- when String
33
- delete_config_list_params
34
- when Array
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
- if active_scaffold_config.config_list.save_to_user && active_scaffold_current_user
75
- active_scaffold_current_user.send(active_scaffold_config.config_list.save_to_user, config_list_session_storage_key, config_list_controller_name).destroy
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 active_scaffold_config.config_list.save_to_user && active_scaffold_current_user
87
- record = active_scaffold_current_user.send(active_scaffold_config.config_list.save_to_user, config_list_session_storage_key, config_list_controller_name)
88
- record.config_list = config_list.join(',')
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? @config_list_record
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
- active_scaffold_current_user.send(active_scaffold_config.config_list.save_to_user, config_list_session_storage_key, config_list_controller_name)
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 config_list_record
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
@@ -1,8 +1,8 @@
1
1
  module ActiveScaffoldConfigList
2
2
  module Version
3
- MAJOR = 3
4
- MINOR = 6
5
- PATCH = 3
3
+ MAJOR = 4
4
+ MINOR = 0
5
+ PATCH = 0
6
6
 
7
7
  STRING = [MAJOR, MINOR, PATCH].compact.join('.')
8
8
  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: 3.6.3
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-01-02 00:00:00.000000000 Z
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: 3.7.1
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: 3.7.1
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