netzke-basepack 0.4.2 → 0.5.1

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.
Files changed (62) hide show
  1. data/.autotest +1 -0
  2. data/.gitignore +6 -0
  3. data/{CHANGELOG → CHANGELOG.rdoc} +26 -0
  4. data/README.rdoc +11 -11
  5. data/Rakefile +37 -11
  6. data/TODO.rdoc +8 -0
  7. data/VERSION +1 -0
  8. data/javascripts/basepack.js +71 -28
  9. data/lib/app/models/netzke_auto_column.rb +56 -0
  10. data/lib/netzke-basepack.rb +5 -3
  11. data/lib/netzke/accordion_panel.rb +69 -67
  12. data/lib/netzke/active_record/basepack.rb +104 -0
  13. data/lib/netzke/active_record/data_accessor.rb +33 -0
  14. data/lib/netzke/basic_app.rb +233 -124
  15. data/lib/netzke/border_layout_panel.rb +97 -98
  16. data/lib/netzke/configuration_panel.rb +24 -0
  17. data/lib/netzke/data_accessor.rb +71 -0
  18. data/lib/netzke/ext.rb +6 -0
  19. data/lib/netzke/field_model.rb +1 -1
  20. data/lib/netzke/fields_configurator.rb +62 -37
  21. data/lib/netzke/form_panel.rb +161 -51
  22. data/lib/netzke/form_panel_api.rb +74 -0
  23. data/lib/netzke/form_panel_js.rb +129 -0
  24. data/lib/netzke/grid_panel.rb +385 -80
  25. data/lib/netzke/grid_panel_api.rb +352 -0
  26. data/lib/netzke/grid_panel_extras/javascripts/rows-dd.js +280 -0
  27. data/lib/netzke/grid_panel_js.rb +721 -0
  28. data/lib/netzke/masquerade_selector.rb +53 -0
  29. data/lib/netzke/panel.rb +9 -0
  30. data/lib/netzke/plugins/configuration_tool.rb +121 -0
  31. data/lib/netzke/property_editor.rb +95 -7
  32. data/lib/netzke/property_editor_extras/helper_model.rb +55 -34
  33. data/lib/netzke/search_panel.rb +62 -0
  34. data/lib/netzke/tab_panel.rb +97 -37
  35. data/lib/netzke/table_editor.rb +49 -44
  36. data/lib/netzke/tree_panel.rb +15 -16
  37. data/lib/netzke/wrapper.rb +29 -5
  38. data/netzke-basepack.gemspec +151 -19
  39. data/stylesheets/basepack.css +5 -0
  40. data/test/app_root/app/models/book.rb +1 -1
  41. data/test/app_root/db/migrate/20081222035855_create_netzke_preferences.rb +1 -1
  42. data/test/unit/accordion_panel_test.rb +1 -2
  43. data/test/unit/active_record_basepack_test.rb +54 -0
  44. data/test/unit/grid_panel_test.rb +8 -12
  45. data/test/unit/helper_model_test.rb +30 -0
  46. metadata +69 -78
  47. data/Manifest +0 -86
  48. data/TODO +0 -3
  49. data/lib/app/models/netzke_hash_record.rb +0 -180
  50. data/lib/app/models/netzke_layout_item.rb +0 -11
  51. data/lib/netzke/ar_ext.rb +0 -269
  52. data/lib/netzke/configuration_tool.rb +0 -80
  53. data/lib/netzke/container.rb +0 -77
  54. data/lib/netzke/db_fields.rb +0 -44
  55. data/lib/netzke/fields_configurator_old.rb +0 -62
  56. data/lib/netzke/form_panel_extras/interface.rb +0 -56
  57. data/lib/netzke/form_panel_extras/js_builder.rb +0 -134
  58. data/lib/netzke/grid_panel_extras/interface.rb +0 -206
  59. data/lib/netzke/grid_panel_extras/js_builder.rb +0 -352
  60. data/test/unit/ar_ext_test.rb +0 -53
  61. data/test/unit/netzke_hash_record_test.rb +0 -52
  62. data/test/unit/netzke_layout_item_test.rb +0 -28
@@ -1,135 +1,440 @@
1
1
  require 'searchlogic'
2
2
 
3
3
  module Netzke
4
+ # == GridPanel
5
+ # Ext.grid.EditorGridPanel + server-side code
4
6
  #
5
- # GridPanel
6
- #
7
- # Functionality:
8
- # * data operations - get, post, delete, create
9
- # * column resize and move
10
- # * column hide - TODO
7
+ # == Features:
8
+ # * multi-line CRUD operations - get, post, delete, create
9
+ # * (multe-record) editing and adding records through a form
10
+ # * column resize, move and hide
11
11
  # * permissions
12
12
  # * sorting
13
13
  # * pagination
14
14
  # * filtering
15
- # * properties and column configuration
15
+ # * extended configurable search
16
+ # * rows reordering (drag-n-drop)
17
+ # * dynamic configuration of properties and columns
16
18
  #
19
+ # == Class configuration
20
+ # Configuration on this level is effective during the life-time of the application. They can be put into a .rb file
21
+ # inside of config/initializers like this:
22
+ #
23
+ # Netzke::GridPanel.configure :column_filters_available, false
24
+ # Netzke::GridPanel.configure :default_config => {:ext_config => {:enable_config_tool => false}}
25
+ #
26
+ # Most of these options directly influence the amount of JavaScript code that is generated for this widget's class.
27
+ # The less functionality is enabled, the less code is generated.
28
+ #
29
+ # The following configuration options are available:
30
+ # * <tt>:column_filters_available</tt> - (default is true) include code for the filters in the column's context menu
31
+ # * <tt>:config_tool_available</tt> - (default is true) include code for the configuration tool that launches the configuration panel
32
+ # * <tt>:edit_in_form_available</tt> - (defaults to true) include code for (multi-record) editing and adding records through a form
33
+ # * <tt>:extended_search_available</tt> - (defaults to true) include code for extended configurable search
34
+ # * <tt>:default_config</tt> - a hash of default configuration options for each instance of the GridPanel widget.
35
+ # See the "Instance configuration" section below.
36
+ #
37
+ # == Instance configuration
38
+ # The following config options are available:
39
+ # * <tt>:data_class_name</tt> - name of the ActiveRecord model that provides data to this GridPanel.
40
+ # * <tt>:strong_default_attrs</tt> - a hash of attributes to be merged atop of every created/updated record.
41
+ # * <tt>:scopes</tt> - an array of searchlogic-compatible scopes to filter grid data like this:
42
+ #
43
+ # ["user_id_not", 100]
44
+ #
45
+ # In the <tt>:ext_config</tt> hash (see Netzke::Base) the following GridPanel specific options are available:
46
+ #
47
+ # * <tt>:enable_column_filters</tt> - enable filters in column's context menu
48
+ # * <tt>:enable_edit_in_form</tt> - provide buttons into the toolbar that activate editing/adding records via a form
49
+ # * <tt>:enable_extended_search</tt> - provide a button into the toolbar that shows configurable search form
50
+ # * <tt>:enable_context_menu</tt> - enable rows context menu
51
+ # * <tt>:enable_rows_reordering</tt> - enable reordering of rows with drag-n-drop; underlying model (specified in <tt>:data_class_name</tt>) must implement "acts_as_list"-compatible functionality; defaults to <tt>false</tt>
52
+ # * <tt>:enable_pagination</tt> - enable pagination; defaults to <tt>true</tt>
53
+ # * <tt>:rows_per_page</tt> - number of rows per page (ignored when <tt>:enable_pagination</tt> is set to <tt>false</tt>)
54
+ # * <tt>:load_inline_data</tt> - load initial data into the grid right after its instantiation (saves a request to server); defaults to <tt>true</tt>
55
+ # * <tt>:mode</tt> - when set to <tt>:config</tt>, GridPanel loads in configuration mode
56
+ #
57
+ # Additionally supports Netzke::Base config options.
17
58
  class GridPanel < Base
18
- # Class-level configuration and its defaults
59
+ # javascript (client-side)
60
+ include Netzke::GridPanelJs
61
+ # API (server-side)
62
+ include Netzke::GridPanelApi
63
+ # Code shared between GridPanel, FormPanel, and other widgets that serve as interface to database tables
64
+ include Netzke::DataAccessor
65
+
66
+ def self.enforce_config_consistency
67
+ config[:default_config][:ext_config][:enable_edit_in_form] &&= config[:edit_in_form_available]
68
+ config[:default_config][:ext_config][:enable_extended_search] &&= config[:extended_search_available]
69
+ config[:default_config][:ext_config][:enable_rows_reordering] &&= config[:rows_reordering_available]
70
+ end
71
+
72
+ # Class-level configuration. This options directly influence the amount of generated
73
+ # javascript code for this widget's class. For example, if you don't want filters for the grid,
74
+ # set :column_filters_available to false, and the javascript for the filters won't be included at all.
19
75
  def self.config
20
76
  set_default_config({
21
- :enable_filters => true,
22
- :config_tool_enabled_by_default => false,
23
- :column_move_enabled_by_default => true,
24
- :column_hide_enabled_by_default => true,
25
- :column_resize_enabled_by_default => true,
26
- :persistent_layout_enabled_by_default => true,
27
- :persistent_config_enabled_by_default => true
77
+
78
+ :column_filters_available => true,
79
+ :config_tool_available => true,
80
+ :edit_in_form_available => true,
81
+ :extended_search_available => true,
82
+ :rows_reordering_available => false,
83
+
84
+ :default_config => {
85
+ :ext_config => {
86
+ :enable_edit_in_form => true,
87
+ :enable_extended_search => true,
88
+ :enable_column_filters => true,
89
+ :load_inline_data => true,
90
+ :enable_context_menu => true,
91
+
92
+ :enable_pagination => true,
93
+ :rows_per_page => 25,
94
+
95
+ :mode => :normal, # when set to :config, :configuration button is enabled
96
+
97
+ :enable_rows_reordering => false, # drag n drop
98
+
99
+ :tools => %w{ refresh }
100
+ },
101
+
102
+ :persistent_config => false
103
+ }
28
104
  })
29
105
  end
30
106
 
31
- include Netzke::GridPanelExtras::JsBuilder
32
- include Netzke::GridPanelExtras::Interface
33
- include Netzke::DbFields # database field operations
107
+ # Include extra javascript that we depend on
108
+ def self.include_js
109
+ res = []
110
+
111
+ # Checkcolumn
112
+ res << "#{File.dirname(__FILE__)}/grid_panel_extras/javascripts/check-column.js"
113
+
114
+ # Filters
115
+ if config[:column_filters_available]
116
+ ext_examples = Netzke::Base.config[:ext_location] + "/examples/"
117
+ res << ext_examples + "grid-filtering/menu/EditableItem.js"
118
+ res << ext_examples + "grid-filtering/menu/RangeMenu.js"
119
+ res << ext_examples + "grid-filtering/grid/GridFilters.js"
34
120
 
35
- # javascripts for grid-filtering (from Ext examples)
36
- if Netzke::GridPanel.config[:enable_filters]
37
- js_include :ext_examples => %w{grid-filtering/menu/EditableItem.js grid-filtering/menu/RangeMenu.js grid-filtering/grid/GridFilters.js}
38
-
39
- js_include :ext_examples => %w{Boolean Date List Numeric String}.unshift("").map{|f| "grid-filtering/grid/filter/#{f}Filter.js" }
121
+ %w{Boolean Date List Numeric String}.unshift("").each do |f|
122
+ res << ext_examples + "grid-filtering/grid/filter/#{f}Filter.js"
123
+ end
124
+
125
+ res << "#{File.dirname(__FILE__)}/grid_panel_extras/javascripts/filters.js"
126
+
127
+ end
40
128
 
41
- js_include "#{File.dirname(__FILE__)}/grid_panel_extras/javascripts/filters.js"
129
+ # DD
130
+ if config[:rows_reordering_available]
131
+ res << "#{File.dirname(__FILE__)}/grid_panel_extras/javascripts/rows-dd.js"
132
+ end
133
+
134
+ res
42
135
  end
136
+
137
+ # Define connection points between client side and server side of GridPanel.
138
+ # See implementation of equally named methods in the GridPanelApi module.
139
+ api :get_data, :post_data, :delete_data, :resize_column, :move_column, :hide_column, :get_combobox_options, :move_rows
43
140
 
44
- # extra javascripts
45
- js_include "#{File.dirname(__FILE__)}/grid_panel_extras/javascripts/check-column.js"
46
-
47
- # define connection points between client side and server side of GridPanel.
48
- # See implementation of equally named methods in the GridPanelExtras::Interface module.
49
- interface :get_data, :post_data, :delete_data, :resize_column, :move_column, :hide_column, :get_cb_choices
50
-
51
- # widget type for DbFields
52
- # TODO: ugly, rethink
53
- def self.widget_type
54
- :grid
55
- end
56
-
57
- # default instance-level configuration
58
- def initial_config
59
- {
60
- :ext_config => {
61
- :config_tool => self.class.config[:config_tool_enabled_by_default],
62
- :enable_column_filters => self.class.config[:enable_filters],
63
- :enable_column_move => self.class.config[:column_move_enabled_by_default],
64
- :enable_column_hide => self.class.config[:column_hide_enabled_by_default],
65
- :enable_column_resize => self.class.config[:column_resize_enabled_by_default]
66
- },
67
- :persistent_layout => self.class.config[:persistent_layout_enabled_by_default],
68
- :persistent_config => self.class.config[:persistent_config_enabled_by_default]
69
- }
141
+ # Edit in form
142
+ api :create_new_record if config[:edit_in_form_available]
143
+
144
+ def data_class
145
+ @data_class ||= config[:data_class_name].nil? ? raise(ArgumentError, "No data_class_name specified for widget #{id_name}") : config[:data_class_name].constantize
70
146
  end
71
147
 
72
- def initial_dependencies
73
- ["FieldsConfigurator"] # TODO: make this happen automatically
148
+
149
+ def initialize(config = {}, parent = nil)
150
+ super
151
+
152
+ apply_helpers
74
153
  end
75
154
 
155
+ # Columns to be displayed by the FieldConfigurator.
156
+ def self.config_columns
157
+ [
158
+ {:name => :name, :type => :string, :editor => :combobox, :width => 200},
159
+ {:name => :excluded, :type => :boolean, :editor => :checkbox, :width => 40, :header => "Excl"},
160
+ {:name => :value},
161
+ {:name => :header},
162
+ {:name => :hidden, :type => :boolean, :editor => :checkbox},
163
+ {:name => :read_only, :type => :boolean, :editor => :checkbox, :header => "R"},
164
+ {:name => :editor, :type => :string, :editor => {:xtype => :combobox, :options => Netzke::Ext::FORM_FIELD_XTYPES}},
165
+ {:name => :renderer, :type => :string},
166
+ # {:name => :renderer, :type => :string, :editor => {:xtype => :jsonfield}},
167
+ {:name => :with_filters, :type => :boolean, :editor => :checkbox, :default => true, :header => "Filters"},
168
+
169
+ # some rarely used configurations, hidden
170
+ {:name => :width, :type => :integer, :editor => :numberfield, :hidden => true},
171
+ {:name => :hideable, :type => :boolean, :editor => :checkbox, :default => true, :hidden => true},
172
+ {:name => :sortable, :type => :boolean, :editor => :checkbox, :default => true, :hidden => true},
173
+ ]
174
+ end
175
+
176
+ def self.property_fields
177
+ res = [
178
+ {:name => :ext_config__title, :type => :string},
179
+ {:name => :ext_config__header, :type => :boolean, :default => true},
180
+ {:name => :ext_config__enable_context_menu, :type => :boolean, :default => true},
181
+ {:name => :ext_config__context_menu, :type => :json},
182
+ {:name => :ext_config__enable_pagination, :type => :boolean, :default => true},
183
+ {:name => :ext_config__rows_per_page, :type => :integer},
184
+ # {:name => :ext_config__bbar, :type => :json},
185
+ {:name => :ext_config__prohibit_create, :type => :boolean},
186
+ {:name => :ext_config__prohibit_update, :type => :boolean},
187
+ {:name => :ext_config__prohibit_delete, :type => :boolean},
188
+ {:name => :ext_config__prohibit_read, :type => :boolean}
189
+ ]
190
+
191
+ res << {:name => :ext_config__enable_extended_search, :type => :boolean} if config[:extended_search_available]
192
+ res << {:name => :ext_config__enable_edit_in_form, :type => :boolean} if config[:edit_in_form_available]
193
+
194
+ res
195
+
196
+ end
197
+
198
+ def independent_config
199
+ res = super
200
+
201
+ # Bottom bar
202
+ if res[:ext_config][:bbar].nil?
203
+ res[:ext_config][:bbar] = %w{ add edit apply del }
204
+ res[:ext_config][:bbar] << "-" << "add_in_form" << "edit_in_form" if res[:ext_config][:enable_edit_in_form]
205
+ res[:ext_config][:bbar] << "-" << "search" if res[:ext_config][:enable_extended_search]
206
+ end
207
+
208
+ # Context menu
209
+ res[:ext_config][:context_menu] ||= default_context_menu(res)
210
+
211
+ res
212
+ end
213
+
214
+ def default_context_menu(indep_config)
215
+ res = %w{ edit del }
216
+ res << "-" << "edit_in_form" if indep_config[:ext_config][:enable_edit_in_form]
217
+ res
218
+ end
219
+
76
220
  def configuration_widgets
77
221
  res = []
78
222
  res << {
223
+ :persistent_config => true,
79
224
  :name => 'columns',
80
225
  :widget_class_name => "FieldsConfigurator",
81
226
  :active => true,
82
- :widget => self,
83
- :persistent_layout => true
84
- } if config[:persistent_layout]
85
-
227
+ :widget => self
228
+ }
86
229
  res << {
87
230
  :name => 'general',
88
231
  :widget_class_name => "PropertyEditor",
89
- :widget_name => id_name,
232
+ :widget => self,
90
233
  :ext_config => {:title => false}
91
234
  }
92
-
93
235
  res
94
236
  end
95
237
 
96
- def tools
97
- %w{ refresh }
98
- end
99
-
100
238
  def actions
101
- { :add => {:text => 'Add', :disabled => !@permissions[:create]},
102
- :edit => {:text => 'Edit', :disabled => !@permissions[:update]},
103
- :delete => {:text => 'Delete', :disabled => !@permissions[:delete]},
104
- :apply => {:text => 'Apply', :disabled => !@permissions[:update] && !@permissions[:create]}
239
+ # Defaults
240
+ res = {
241
+ :add => {:text => 'Add', :disabled => ext_config[:prohibit_create]},
242
+ :edit => {:text => 'Edit', :disabled => true},
243
+ :del => {:text => 'Delete', :disabled => true},
244
+ :apply => {:text => 'Apply', :disabled => ext_config[:prohibit_update] && ext_config[:prohibit_create]}
105
245
  }
246
+
247
+ # Edit in form
248
+ res.merge!({
249
+ :add_in_form => {:text => 'Add in form'},
250
+ :edit_in_form => {:text => 'Edit in form', :disabled => true}
251
+ }) if ext_config[:enable_edit_in_form]
252
+
253
+ # Extended search
254
+ res.merge!({
255
+ :search => {:text => 'Search'}
256
+ }) if ext_config[:enable_extended_search]
257
+
258
+ res
106
259
  end
107
260
 
108
- def bbar
109
- persistent_config[:bottom_bar] ||= config[:bbar] == false ? nil : config[:bbar] || %w{ add edit apply delete }
261
+ def initial_late_aggregatees
262
+ res = {}
263
+
264
+ # Edit in form
265
+ res.merge!({
266
+ :edit_form => {
267
+ :widget_class_name => "FormPanel",
268
+ :persistent_config => true,
269
+ :data_class_name => config[:data_class_name],
270
+ :ext_config => {
271
+ :bbar => false,
272
+ :header => false,
273
+ :mode => ext_config[:mode]
274
+ }
275
+ },
276
+
277
+ :multi_edit_form => {
278
+ :widget_class_name => "FormPanel",
279
+ :persistent_config => true,
280
+ :data_class_name => config[:data_class_name],
281
+ :ext_config => {
282
+ :bbar => false,
283
+ :header => false,
284
+ :mode => ext_config[:mode]
285
+ }
286
+ },
287
+
288
+ :new_record_form => {
289
+ :widget_class_name => "FormPanel",
290
+ :persistent_config => true,
291
+ :data_class_name => config[:data_class_name],
292
+ :ext_config => {
293
+ :bbar => false,
294
+ :header => false,
295
+ :mode => ext_config[:mode]
296
+ },
297
+ :record => config[:data_class_name].constantize.new
298
+ }
299
+ }) if ext_config[:enable_edit_in_form]
300
+
301
+ # Extended search
302
+ res.merge!({
303
+ :search_panel => {
304
+ :widget_class_name => "SearchPanel",
305
+ :search_class_name => config[:data_class_name],
306
+ :persistent_config => true,
307
+ :ext_config => {
308
+ :header => false,
309
+ :bbar => false,
310
+ :mode => ext_config[:mode]
311
+ },
312
+ }
313
+ }) if ext_config[:enable_extended_search]
314
+
315
+ res
110
316
  end
111
317
 
318
+
319
+ include Plugins::ConfigurationTool if config[:config_tool_available] # it will load ConfigurationPanel into a modal window
320
+
112
321
  def columns
113
322
  @columns ||= get_columns
114
323
  end
115
-
116
- include ConfigurationTool # it will load aggregation with name :properties into a modal window
117
-
118
- protected
119
-
120
- def available_permissions
121
- %w(read update create delete)
324
+
325
+ # Normalized columns
326
+ def normalized_columns
327
+ @normalized_columns ||= normalize_columns(columns)
122
328
  end
123
329
 
124
330
  def get_columns
125
- if config[:persistent_layout]
126
- NetzkeLayoutItem.widget = id_name
127
- NetzkeLayoutItem.data = default_db_fields if NetzkeLayoutItem.all.empty?
128
- NetzkeLayoutItem.all
331
+ if config[:persistent_config]
332
+ persistent_config['layout__columns'] ||= default_columns
333
+ res = normalize_array_of_columns(persistent_config['layout__columns'])
334
+ else
335
+ res = default_columns
336
+ end
337
+
338
+ # denormalize
339
+ res.map{ |c| c.is_a?(Hash) && c.reject{ |k,v| k == :name }.empty? ? c[:name].to_sym : c }
340
+ end
341
+
342
+ # Normalizes the column at position +index+ and returns it.
343
+ def column_at(index)
344
+ if columns[index].is_a?(Hash)
345
+ columns[index]
129
346
  else
130
- default_db_fields
347
+ column_name = columns.delete_at(index)
348
+ normalized_column = normalize_column(column_name)
349
+ columns.insert(index, normalized_column)
350
+ normalized_column
131
351
  end
132
352
  end
133
353
 
354
+ # Stores modified columns in persistent storage
355
+ def save_columns!
356
+ persistent_config[:layout__columns] = columns
357
+ end
358
+
359
+ TYPE_EDITOR_MAP = {
360
+ :integer => :numberfield,
361
+ :boolean => :checkbox,
362
+ :date => :datefield,
363
+ :datetime => :xdatetime,
364
+ :text => :textarea
365
+ # :string => :textfield
366
+ }
367
+
368
+ def default_columns
369
+ # columns specified in widget's config
370
+ columns_from_config = config[:columns] && normalize_columns(config[:columns])
371
+
372
+ if columns_from_config
373
+ # reverse-merge each column hash from config with each column hash from exposed_attributes (columns from config have higher priority)
374
+ for c in columns_from_config
375
+ corresponding_exposed_column = predefined_columns.find{ |k| k[:name] == c[:name] }
376
+ c.reverse_merge!(corresponding_exposed_column) if corresponding_exposed_column
377
+ end
378
+ columns_for_create = columns_from_config
379
+ else
380
+ # we didn't have columns configured in widget's config, so, use the columns from the data class
381
+ columns_for_create = predefined_columns
382
+ end
383
+
384
+ columns_for_create.map! do |c|
385
+ # detect ActiveRecord column type (if the column is "real") or fall back to :virtual
386
+ type = (data_class.columns_hash[c[:name].to_s] && data_class.columns_hash[c[:name].to_s].type) || :virtual
387
+
388
+ # detect :assoc__method columns
389
+ if c[:name].to_s.index('__')
390
+ assoc_name, method = c[:name].to_s.split('__').map(&:to_sym)
391
+ if assoc = data_class.reflect_on_association(assoc_name)
392
+ assoc_column = assoc.klass.columns_hash[method.to_s]
393
+ assoc_method_type = assoc_column.try(:type)
394
+ if assoc_method_type
395
+ c[:editor] ||= TYPE_EDITOR_MAP[assoc_method_type] == :checkbox ? :checkbox : :combobox
396
+ end
397
+ type = :association
398
+ end
399
+ end
400
+
401
+ # detect association column (e.g. :category_id)
402
+ assoc = data_class.reflect_on_all_associations.detect{|a| a.primary_key_name.to_sym == c[:name]}
403
+ if assoc && !assoc.options[:polymorphic]
404
+ c[:editor] ||= :combobox
405
+ assoc_method = %w{name title label id}.detect{|m| (assoc.klass.instance_methods + assoc.klass.column_names).include?(m) } || assoc.klass.primary_key
406
+ c[:name] = "#{assoc.name}__#{assoc_method}".to_sym
407
+ type = :association
408
+ end
409
+
410
+ # Some smart defaults
411
+
412
+ # default editor, dependent on column type
413
+ c[:editor] ||= TYPE_EDITOR_MAP[type] unless TYPE_EDITOR_MAP[type].nil?
414
+ # narrow column for checkbox
415
+ c[:width] ||= 50 if c[:editor] == :checkbox
416
+ # wider column for xdatetime
417
+ c[:width] ||= 120 if c[:editor] == :xdatetime
418
+ # hide ID column
419
+ c[:hidden] = true if c[:name] == data_class.primary_key.to_sym && c[:hidden].nil?
420
+
421
+ # Some default limitations for virtual columns
422
+ if type == :virtual
423
+ # disable filters
424
+ c[:with_filters].nil? && c[:with_filters] = false
425
+ # disable sorting
426
+ c[:sortable].nil? && c[:sortable] = false
427
+ # read-only
428
+ c[:read_only].nil? && c[:read_only] = true
429
+ end
430
+
431
+ # denormalize column (save space)
432
+ c.reject{ |k,v| k == :name }.empty? ? c[:name] : c
433
+ end
434
+
435
+ columns_for_create
436
+
437
+ end
438
+
134
439
  end
135
440
  end