skozlov-netzke-basepack 0.1.1.2 → 0.5.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.
Files changed (81) hide show
  1. data/.autotest +1 -0
  2. data/.gitignore +5 -0
  3. data/LICENSE +2 -19
  4. data/README.rdoc +87 -0
  5. data/Rakefile +28 -12
  6. data/TODO.rdoc +7 -0
  7. data/VERSION +1 -0
  8. data/autotest/discover.rb +3 -0
  9. data/init.rb +0 -1
  10. data/javascripts/basepack.js +839 -49
  11. data/lib/app/models/netzke_auto_column.rb +56 -0
  12. data/lib/netzke/accordion_panel.rb +113 -0
  13. data/lib/netzke/active_record/basepack.rb +104 -0
  14. data/lib/netzke/active_record/data_accessor.rb +21 -0
  15. data/lib/netzke/basic_app.rb +325 -0
  16. data/lib/netzke/border_layout_panel.rb +128 -0
  17. data/lib/netzke/configuration_panel.rb +24 -0
  18. data/lib/netzke/data_accessor.rb +71 -0
  19. data/lib/netzke/ext.rb +6 -0
  20. data/lib/netzke/field_model.rb +131 -0
  21. data/lib/netzke/fields_configurator.rb +95 -0
  22. data/lib/netzke/form_panel.rb +214 -0
  23. data/lib/netzke/form_panel_api.rb +74 -0
  24. data/lib/netzke/form_panel_extras/javascripts/xcheckbox.js +82 -0
  25. data/lib/netzke/form_panel_js.rb +129 -0
  26. data/lib/netzke/grid_panel.rb +442 -0
  27. data/lib/netzke/grid_panel_api.rb +352 -0
  28. data/lib/netzke/grid_panel_extras/javascripts/check-column.js +33 -0
  29. data/{javascripts → lib/netzke/grid_panel_extras/javascripts}/filters.js +0 -0
  30. data/lib/netzke/grid_panel_extras/javascripts/rows-dd.js +280 -0
  31. data/lib/netzke/grid_panel_js.rb +721 -0
  32. data/lib/netzke/panel.rb +13 -0
  33. data/lib/netzke/plugins/configuration_tool.rb +121 -0
  34. data/lib/netzke/property_editor.rb +105 -0
  35. data/lib/netzke/property_editor_extras/helper_model.rb +126 -0
  36. data/lib/netzke/search_panel.rb +62 -0
  37. data/lib/netzke/tab_panel.rb +160 -0
  38. data/lib/netzke/table_editor.rb +118 -0
  39. data/lib/netzke/tree_panel.rb +73 -0
  40. data/lib/netzke/wrapper.rb +42 -0
  41. data/lib/netzke-basepack.rb +9 -15
  42. data/netzke-basepack.gemspec +147 -20
  43. data/stylesheets/basepack.css +26 -0
  44. data/test/app_root/app/models/book.rb +1 -1
  45. data/test/app_root/config/environment.rb +1 -0
  46. data/test/app_root/db/migrate/20081222033440_create_genres.rb +1 -0
  47. data/test/app_root/db/migrate/20081222035855_create_netzke_preferences.rb +1 -1
  48. data/test/app_root/db/migrate/20090102223630_create_netzke_layouts.rb +14 -0
  49. data/test/app_root/vendor/plugins/acts_as_list/README +23 -0
  50. data/test/app_root/vendor/plugins/acts_as_list/init.rb +3 -0
  51. data/test/app_root/vendor/plugins/acts_as_list/lib/active_record/acts/list.rb +256 -0
  52. data/test/test_helper.rb +1 -2
  53. data/test/unit/accordion_panel_test.rb +20 -0
  54. data/test/unit/active_record_basepack_test.rb +54 -0
  55. data/test/unit/grid_panel_test.rb +43 -0
  56. data/test/unit/helper_model_test.rb +30 -0
  57. data/test/unit/netzke_basepack_test.rb +4 -0
  58. data/test/unit/tab_panel_test.rb +21 -0
  59. metadata +96 -72
  60. data/CHANGELOG +0 -14
  61. data/Manifest +0 -65
  62. data/README.mdown +0 -18
  63. data/generators/netzke_basepack/USAGE +0 -8
  64. data/generators/netzke_basepack/netzke_basepack_generator.rb +0 -8
  65. data/generators/netzke_basepack/netzke_grid_generator.rb +0 -7
  66. data/generators/netzke_basepack/templates/create_netzke_grid_columns.rb +0 -21
  67. data/lib/app/models/netzke_grid_column.rb +0 -23
  68. data/lib/netzke/accordion.rb +0 -11
  69. data/lib/netzke/ar_ext.rb +0 -163
  70. data/lib/netzke/column.rb +0 -43
  71. data/lib/netzke/container.rb +0 -81
  72. data/lib/netzke/grid.rb +0 -120
  73. data/lib/netzke/grid_interface.rb +0 -156
  74. data/lib/netzke/grid_js_builder.rb +0 -276
  75. data/lib/netzke/preference_grid.rb +0 -43
  76. data/lib/netzke/properties_tool.rb +0 -66
  77. data/lib/netzke/property_grid.rb +0 -60
  78. data/test/ar_ext_test.rb +0 -39
  79. data/test/column_test.rb +0 -27
  80. data/test/grid_test.rb +0 -43
  81. data/test/netzke_basepack_test.rb +0 -8
@@ -0,0 +1,442 @@
1
+ require 'searchlogic'
2
+
3
+ module Netzke
4
+ # == GridPanel
5
+ # Ext.grid.EditorGridPanel + server-side code
6
+ #
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
+ # * permissions
12
+ # * sorting
13
+ # * pagination
14
+ # * filtering
15
+ # * extended configurable search
16
+ # * rows reordering (drag-n-drop)
17
+ # * dynamic configuration of properties and columns
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.
58
+ class GridPanel < Base
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.
75
+ def self.config
76
+ set_default_config({
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
+ }
104
+ })
105
+ end
106
+
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"
120
+
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
128
+
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
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
140
+
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
146
+ end
147
+
148
+
149
+ def initialize(config = {}, parent = nil)
150
+ super
151
+
152
+ apply_helpers
153
+ end
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
+
220
+ def configuration_widgets
221
+ res = []
222
+ res << {
223
+ :persistent_config => true,
224
+ :name => 'columns',
225
+ :widget_class_name => "FieldsConfigurator",
226
+ :active => true,
227
+ :widget => self
228
+ }
229
+ res << {
230
+ :name => 'general',
231
+ :widget_class_name => "PropertyEditor",
232
+ :widget => self,
233
+ :ext_config => {:title => false}
234
+ }
235
+ res
236
+ end
237
+
238
+ def actions
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]}
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
259
+ end
260
+
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
316
+ end
317
+
318
+
319
+ include Plugins::ConfigurationTool if config[:config_tool_available] # it will load ConfigurationPanel into a modal window
320
+
321
+ def columns
322
+ @columns ||= get_columns
323
+ end
324
+
325
+ # Normalized columns
326
+ def normalized_columns
327
+ @normalized_columns ||= normalize_columns(columns)
328
+ end
329
+
330
+ def get_columns
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]
346
+ else
347
+ column_name = columns.delete_at(index)
348
+ normalized_column = normalize_column(column_name)
349
+ columns.insert(index, normalized_column)
350
+ normalized_column
351
+ end
352
+ end
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
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
+ if assoc = data_class.reflect_on_all_associations.detect{|a| a.primary_key_name.to_sym == c[:name]}
403
+ c[:editor] ||= :combobox
404
+ assoc_method = %w{name title label id}.detect{|m| (assoc.klass.instance_methods + assoc.klass.column_names).include?(m) } || assoc.klass.primary_key
405
+ c[:name] = "#{assoc.name}__#{assoc_method}".to_sym
406
+ type = :association
407
+ end
408
+
409
+ # Some smart defaults
410
+
411
+ # default editor, dependent on column type
412
+ c[:editor] ||= TYPE_EDITOR_MAP[type] unless TYPE_EDITOR_MAP[type].nil?
413
+
414
+ # narrow column for checkbox
415
+ c[:width] ||= 50 if c[:editor] == :checkbox
416
+
417
+ # wider column for xdatetime
418
+ c[:width] ||= 120 if c[:editor] == :xdatetime
419
+
420
+ # hide ID column
421
+ c[:hidden] = true if c[:name] == data_class.primary_key.to_sym && c[:hidden].nil?
422
+
423
+ # Some default limitations for virtual columns
424
+ if type == :virtual
425
+ # disable filters
426
+ c[:with_filters].nil? && c[:with_filters] = false
427
+ # disable sorting
428
+ c[:sortable].nil? && c[:sortable] = false
429
+ # read-only
430
+ c[:read_only].nil? && c[:read_only] = true
431
+ end
432
+
433
+ # denormalize column (save space)
434
+ c.reject{ |k,v| k == :name }.empty? ? c[:name] : c
435
+ end
436
+
437
+ columns_for_create
438
+
439
+ end
440
+
441
+ end
442
+ end
@@ -0,0 +1,352 @@
1
+ module Netzke
2
+ module GridPanelApi
3
+ def post_data(params)
4
+ success = true
5
+ mod_records = {}
6
+ [:create, :update].each do |operation|
7
+ data = ActiveSupport::JSON.decode(params["#{operation}d_records"]) if params["#{operation}d_records"]
8
+ if !data.nil? && !data.empty? # data may be nil for one of the operations
9
+ mod_records[operation] = process_data(data, operation)
10
+ mod_records[operation] = nil if mod_records[operation].empty?
11
+ end
12
+ break if !success
13
+ end
14
+ {
15
+ :update_new_records => mod_records[:create],
16
+ :update_mod_records => mod_records[:update] || {},
17
+ :feedback => @flash
18
+ }
19
+ end
20
+
21
+ def get_data(params = {})
22
+ if !ext_config[:prohibit_read]
23
+ records = get_records(params)
24
+ {:data => records.map{|r| r.to_array(normalized_columns)}, :total => ext_config[:enable_pagination] && records.total_entries}
25
+ else
26
+ flash :error => "You don't have permissions to read data"
27
+ {:feedback => @flash}
28
+ end
29
+ end
30
+
31
+ def delete_data(params = {})
32
+ if !ext_config[:prohibit_delete]
33
+ record_ids = ActiveSupport::JSON.decode(params[:records])
34
+ klass = config[:data_class_name].constantize
35
+ klass.delete(record_ids)
36
+ {:feedback => "Deleted #{record_ids.size} record(s)", :load_store_data => get_data}
37
+ else
38
+ {:feedback => "You don't have permissions to delete data"}
39
+ end
40
+ end
41
+
42
+ def resize_column(params)
43
+ raise "Called api_resize_column while not configured to do so" if ext_config[:enable_column_resize] == false
44
+ column_at(params[:index].to_i)[:width] = params[:size].to_i
45
+ save_columns!
46
+ {}
47
+ end
48
+
49
+ def hide_column(params)
50
+ raise "Called api_hide_column while not configured to do so" if ext_config[:enable_column_hide] == false
51
+ column_at(params[:index].to_i)[:hidden] = params[:hidden].to_b
52
+ save_columns!
53
+ {}
54
+ end
55
+
56
+ def move_column(params)
57
+ raise "Called api_move_column while not configured to do so" if ext_config[:enable_column_move] == false
58
+ column_to_move = columns.delete_at(params[:old_index].to_i)
59
+ columns.insert(params[:new_index].to_i, column_to_move)
60
+ save_columns!
61
+
62
+ # reorder the columns on the client side (still not sure if it's not an overkill)
63
+ # {:reorder_columns => columns.map(&:name)} # Well, I think it IS an overkill - commented out
64
+ # until proven to be necessary
65
+ {}
66
+ end
67
+
68
+ # Return the choices for the column
69
+ def get_combobox_options(params)
70
+ column = params[:column]
71
+ query = params[:query]
72
+
73
+ {:data => config[:data_class_name].constantize.options_for(column, query).map{|s| [s]}}
74
+ end
75
+
76
+ # Returns searchlogic's search with all the conditions
77
+ def get_search(params)
78
+ @search ||= begin
79
+ raise ArgumentError, "No data_class_name specified for widget '#{name}'" if !config[:data_class_name]
80
+
81
+ # make params coming from Ext grid filters understandable by searchlogic
82
+ search_params = normalize_params(params)
83
+
84
+ # merge with conditions coming from the config
85
+ search_params[:conditions].deep_merge!(config[:conditions] || {})
86
+
87
+ # merge with extra conditions (in searchlogic format, come from the extended search form)
88
+ search_params[:conditions].deep_merge!(
89
+ normalize_extra_conditions(ActiveSupport::JSON.decode(params[:extra_conditions]))
90
+ ) if params[:extra_conditions]
91
+
92
+ search = config[:data_class_name].constantize.search(search_params)
93
+
94
+ # applying scopes
95
+ scopes.each do |s|
96
+ if s.is_a?(Array)
97
+ scope_name, *args = s
98
+ search.send(scope_name, *args)
99
+ else
100
+ search.send(s)
101
+ end
102
+ end
103
+
104
+ search
105
+ end
106
+ end
107
+
108
+ def configuration_panel__columns__get_combobox_options(params)
109
+ query = params[:query]
110
+
111
+ data_arry = case params[:column]
112
+ when "name"
113
+ predefined_columns.map{ |c| c[:name].to_s }
114
+ else
115
+ raise RuntimeError, "Don't know about options for column '#{params[:column]}'"
116
+ end
117
+
118
+ {:data => data_arry.grep(/^#{query}/).map{ |n| [n] }}.to_nifty_json
119
+ end
120
+
121
+ protected
122
+
123
+ # operation => :update || :create
124
+ def process_data(data, operation)
125
+ success = true
126
+ # mod_record_ids = []
127
+ mod_records = {}
128
+ if !ext_config["prohibit_#{operation}".to_sym]
129
+ klass = config[:data_class_name].constantize
130
+ modified_records = 0
131
+ data.each do |record_hash|
132
+ id = record_hash.delete('id')
133
+ record = operation == :create ? klass.new : klass.find(id)
134
+ success = true
135
+
136
+ # merge with strong default attirbutes
137
+ record_hash.merge!(config[:strong_default_attrs]) if config[:strong_default_attrs]
138
+
139
+ # process all attirubutes for this record
140
+ record_hash.each_pair do |k,v|
141
+ begin
142
+ record.send("#{k}=",v)
143
+ rescue ArgumentError => exc
144
+ flash :error => exc.message
145
+ success = false
146
+ break
147
+ end
148
+ end
149
+
150
+ # try to save
151
+ # modified_records += 1 if success && record.save
152
+ mod_records[id] = record.to_array(columns) if success && record.save
153
+ # mod_record_ids << id if success && record.save
154
+
155
+ # flash eventual errors
156
+ if !record.errors.empty?
157
+ success = false
158
+ record.errors.each_full do |msg|
159
+ flash :error => msg
160
+ end
161
+ end
162
+ end
163
+ # flash :notice => "#{operation.to_s.capitalize}d #{modified_records} record(s)"
164
+ else
165
+ success = false
166
+ flash :error => "You don't have permissions to #{operation} data"
167
+ end
168
+ mod_records
169
+ end
170
+
171
+ # get records
172
+ def get_records(params)
173
+ # Restore params from widget_session if requested
174
+ if params[:with_last_params]
175
+ params = widget_session[:last_params]
176
+ else
177
+ # remember the last params
178
+ widget_session[:last_params] = params
179
+ end
180
+
181
+ search = get_search(params)
182
+
183
+ # sorting
184
+ if params[:sort]
185
+ assoc, method = params[:sort].split('__')
186
+ sort_string = method.nil? ? assoc : "#{assoc}_#{method}"
187
+ sort_string = (params[:dir] == "ASC" ? "ascend_by_" : "descend_by_") + sort_string
188
+ search.order(sort_string)
189
+ end
190
+
191
+ # pagination
192
+ if ext_config[:enable_pagination]
193
+ per_page = ext_config[:rows_per_page]
194
+ page = params[:limit] ? params[:start].to_i/params[:limit].to_i + 1 : 1
195
+ search.paginate(:per_page => per_page, :page => page)
196
+ else
197
+ search.all
198
+ end
199
+ end
200
+
201
+ # Create record with form
202
+ def create_new_record(params)
203
+ form_data = ActiveSupport::JSON.decode(params[:data])
204
+ res = aggregatee_instance(:new_record_form).create_or_update_record(form_data)
205
+
206
+ if res[:set_form_values]
207
+ # successful creation
208
+ res[:set_form_values] = nil
209
+ res[:on_successfull_record_creation] = true
210
+ end
211
+ res
212
+ end
213
+
214
+ # Move rows
215
+ def move_rows(params)
216
+ if defined?(ActsAsList) && data_class.ancestors.include?(ActsAsList::InstanceMethods)
217
+ ids = JSON.parse(params[:ids]).reverse
218
+ ids.each_with_index do |id, i|
219
+ r = data_class.find(id)
220
+ r.insert_at(params[:new_index].to_i + i + 1)
221
+ end
222
+ else
223
+ raise RuntimeError, "Data class should 'acts_as_list' to support moving rows"
224
+ end
225
+ {}
226
+ end
227
+
228
+ # When providing the edit_form aggregatee, fill in the form with the requested record
229
+ def load_aggregatee_with_cache(params)
230
+ if params[:id] == 'edit_form'
231
+ super(params.merge(:config => {:record_id => params[:record_id]}.to_json))
232
+ else
233
+ super
234
+ end
235
+ end
236
+
237
+ # Search scopes, in searchlogic format
238
+ def scopes
239
+ @scopes ||= config[:scopes] || []
240
+ end
241
+
242
+ # Converts Ext.grid.GridFilters filters to searchlogic conditions, e.g.
243
+ # {"0" => {
244
+ # "data" => {
245
+ # "type" => "numeric",
246
+ # "comparison" => "gt",
247
+ # "value" => 10 },
248
+ # "field" => "id"
249
+ # },
250
+ # "1" => {
251
+ # "data" => {
252
+ # "type" => "string",
253
+ # "value" => "pizza"
254
+ # },
255
+ # "field" => "food_name"
256
+ # }}
257
+ #
258
+ # =>
259
+ #
260
+ # {"id_gt" => 100, "food_name_contains" => "pizza"}
261
+ def convert_filters(column_filter)
262
+ res = {}
263
+ column_filter.each_pair do |k,v|
264
+ field = v["field"].dup
265
+ case v["data"]["type"]
266
+ when "string"
267
+ field << "_contains"
268
+ when "numeric"
269
+ field << "_#{v["data"]["comparison"]}"
270
+ end
271
+ value = v["data"]["value"]
272
+ res.merge!({field => value})
273
+ end
274
+ res
275
+ end
276
+
277
+ def normalize_extra_conditions(conditions)
278
+ conditions.convert_keys{|k| k.to_s.gsub("__", "_").to_sym}
279
+ end
280
+
281
+ # make params understandable to searchlogic
282
+ def normalize_params(params)
283
+ # filters
284
+ conditions = params[:filter] && convert_filters(params[:filter])
285
+
286
+ normalized_conditions = {}
287
+ conditions && conditions.each_pair do |k, v|
288
+ assoc, method = k.split('__')
289
+ normalized_conditions.merge!(method.nil? ? {assoc => v} : {assoc => {method => v}})
290
+ end
291
+
292
+ {:conditions => normalized_conditions}
293
+ end
294
+
295
+ ## Edit in form specific API
296
+ def new_record_form__submit(params)
297
+ form_data = ActiveSupport::JSON.decode(params[:data])
298
+
299
+ # merge with strong default attirbutes
300
+ form_data.merge!(config[:strong_default_attrs]) if config[:strong_default_attrs]
301
+
302
+ res = aggregatee_instance(:new_record_form).create_or_update_record(form_data)
303
+
304
+ if res[:set_form_values]
305
+ # successful creation
306
+ res[:set_form_values] = nil
307
+ res.merge!({
308
+ :parent => {:on_successfull_record_creation => true}
309
+ })
310
+ end
311
+ res.to_nifty_json
312
+ end
313
+
314
+ def check_for_positive_result(res)
315
+ if res[:set_form_values]
316
+ # successful creation
317
+ res[:set_form_values] = nil
318
+ res.merge!({
319
+ :parent => {:on_successfull_edit => true}
320
+ })
321
+ end
322
+ end
323
+
324
+ def edit_form__submit(params)
325
+ form_data = ActiveSupport::JSON.decode(params[:data])
326
+ res = aggregatee_instance(:new_record_form).create_or_update_record(form_data)
327
+
328
+ check_for_positive_result(res)
329
+
330
+ res.to_nifty_json
331
+ end
332
+
333
+ def multi_edit_form__submit(params)
334
+ form_data = ActiveSupport::JSON.decode(params[:data])
335
+ form_instance = aggregatee_instance(:new_record_form)
336
+
337
+ ids = ActiveSupport::JSON.decode(params[:ids])
338
+
339
+ res = {}
340
+ ids.each do |id|
341
+ res = form_instance.create_or_update_record(form_data.merge("id" => id))
342
+ break if !res[:set_form_values]
343
+ end
344
+
345
+ check_for_positive_result(res)
346
+
347
+ res.to_nifty_json
348
+ end
349
+
350
+
351
+ end
352
+ end