netzke-basepack 0.5.5.1 → 0.5.6

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 (41) hide show
  1. data/CHANGELOG.rdoc +16 -1
  2. data/README.rdoc +31 -56
  3. data/Rakefile +2 -2
  4. data/TODO.rdoc +3 -0
  5. data/javascripts/basepack.js +70 -30
  6. data/lib/app/models/netzke_auto_column.rb +1 -53
  7. data/lib/app/models/netzke_auto_field.rb +4 -0
  8. data/lib/app/models/netzke_auto_table.rb +61 -0
  9. data/lib/netzke-basepack.rb +13 -7
  10. data/lib/netzke/active_record/basepack.rb +28 -0
  11. data/lib/netzke/active_record/data_accessor.rb +2 -0
  12. data/lib/netzke/basic_app.rb +3 -4
  13. data/lib/netzke/{basic_app_extras → basic_app}/statusbar_ext.js +0 -0
  14. data/lib/netzke/data_accessor.rb +2 -0
  15. data/lib/netzke/ext.rb +1 -1
  16. data/lib/netzke/fields_configurator.rb +22 -6
  17. data/lib/netzke/form_panel.rb +20 -11
  18. data/lib/netzke/form_panel/form_panel_api.rb +78 -0
  19. data/lib/netzke/form_panel/form_panel_js.rb +143 -0
  20. data/lib/netzke/{form_panel_extras → form_panel}/javascripts/netzkefileupload.js +0 -0
  21. data/lib/netzke/{form_panel_extras → form_panel}/javascripts/xcheckbox.js +0 -0
  22. data/lib/netzke/grid_panel.rb +33 -24
  23. data/lib/netzke/grid_panel/grid_panel_api.rb +358 -0
  24. data/lib/netzke/grid_panel/grid_panel_js.rb +747 -0
  25. data/lib/netzke/{grid_panel_extras → grid_panel}/javascripts/filters.js +0 -0
  26. data/lib/netzke/{grid_panel_extras → grid_panel}/javascripts/rows-dd.js +0 -0
  27. data/lib/netzke/grid_panel/record_form_window.rb +44 -0
  28. data/lib/netzke/panel.rb +1 -3
  29. data/lib/netzke/property_editor.rb +2 -0
  30. data/lib/netzke/{property_editor_extras → property_editor}/helper_model.rb +2 -2
  31. data/lib/netzke/search_panel.rb +3 -3
  32. data/lib/netzke/window.rb +9 -3
  33. data/test/unit/grid_panel_test.rb +0 -2
  34. data/test/unit/helper_model_test.rb +2 -2
  35. metadata +16 -15
  36. data/lib/netzke/field_model.rb +0 -131
  37. data/lib/netzke/form_panel_api.rb +0 -78
  38. data/lib/netzke/form_panel_js.rb +0 -141
  39. data/lib/netzke/grid_panel_api.rb +0 -356
  40. data/lib/netzke/grid_panel_extras/record_form_window.rb +0 -46
  41. data/lib/netzke/grid_panel_js.rb +0 -725
@@ -1,4 +1,7 @@
1
- require 'searchlogic'
1
+ require "netzke/grid_panel/grid_panel_js"
2
+ require "netzke/grid_panel/grid_panel_api"
3
+ require "netzke/plugins/configuration_tool"
4
+ require "netzke/data_accessor"
2
5
 
3
6
  module Netzke
4
7
  # == GridPanel
@@ -36,7 +39,7 @@ module Netzke
36
39
  #
37
40
  # == Instance configuration
38
41
  # The following config options are available:
39
- # * <tt>:data_class_name</tt> - name of the ActiveRecord model that provides data to this GridPanel.
42
+ # * <tt>:model</tt> - name of the ActiveRecord model that provides data to this GridPanel.
40
43
  # * <tt>:strong_default_attrs</tt> - a hash of attributes to be merged atop of every created/updated record.
41
44
  # * <tt>:scopes</tt> - an array of named scopes to filter grid data, e.g.:
42
45
  #
@@ -48,7 +51,7 @@ module Netzke
48
51
  # * <tt>:enable_edit_in_form</tt> - provide buttons into the toolbar that activate editing/adding records via a form
49
52
  # * <tt>:enable_extended_search</tt> - provide a button into the toolbar that shows configurable search form
50
53
  # * <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>
54
+ # * <tt>:enable_rows_reordering</tt> - enable reordering of rows with drag-n-drop; underlying model (specified in <tt>:model</tt>) must implement "acts_as_list"-compatible functionality; defaults to <tt>false</tt>
52
55
  # * <tt>:enable_pagination</tt> - enable pagination; defaults to <tt>true</tt>
53
56
  # * <tt>:rows_per_page</tt> - number of rows per page (ignored when <tt>:enable_pagination</tt> is set to <tt>false</tt>)
54
57
  # * <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>
@@ -57,9 +60,9 @@ module Netzke
57
60
  # Additionally supports Netzke::Base config options.
58
61
  class GridPanel < Base
59
62
  # javascript (client-side)
60
- include Netzke::GridPanelJs
63
+ include GridPanelJs
61
64
  # API (server-side)
62
- include Netzke::GridPanelApi
65
+ include GridPanelApi
63
66
  # Code shared between GridPanel, FormPanel, and other widgets that serve as interface to database tables
64
67
  include Netzke::DataAccessor
65
68
 
@@ -108,7 +111,7 @@ module Netzke
108
111
  # Checkcolumn
109
112
  ext_examples = Netzke::Base.config[:ext_location] + "/examples/"
110
113
  res << ext_examples + "ux/CheckColumn.js"
111
- # res << "#{File.dirname(__FILE__)}/grid_panel_extras/javascripts/check-column.js"
114
+ # res << "#{File.dirname(__FILE__)}/grid_panel/javascripts/check-column.js"
112
115
 
113
116
 
114
117
  # Filters
@@ -123,13 +126,13 @@ module Netzke
123
126
  # res << ext_examples + "grid-filtering/grid/filter/#{f}Filter.js"
124
127
  # end
125
128
  #
126
- # res << "#{File.dirname(__FILE__)}/grid_panel_extras/javascripts/filters.js"
129
+ # res << "#{File.dirname(__FILE__)}/grid_panel/javascripts/filters.js"
127
130
  #
128
131
  # end
129
132
 
130
133
  # DD
131
134
  if config[:rows_reordering_available]
132
- res << "#{File.dirname(__FILE__)}/grid_panel_extras/javascripts/rows-dd.js"
135
+ res << "#{File.dirname(__FILE__)}/grid_panel/javascripts/rows-dd.js"
133
136
  end
134
137
 
135
138
  res
@@ -142,10 +145,12 @@ module Netzke
142
145
  # Edit in form
143
146
  api :create_new_record if config[:edit_in_form_available]
144
147
 
148
+ # (We can't memoize this method because at some point we extend it, e.g. in Netzke::DataAccessor)
145
149
  def data_class
146
- @data_class ||= config[:data_class_name].nil? ? raise(ArgumentError, "No data_class_name specified for widget #{global_id}") : config[:data_class_name].constantize
150
+ ::ActiveSupport::Deprecation.warn("data_class_name option is deprecated. Use model instead", caller) if config[:data_class_name]
151
+ model_name = config[:model] || config[:data_class_name]
152
+ @data_class ||= model_name.nil? ? raise(ArgumentError, "No model specified for widget #{global_id}") : model_name.constantize
147
153
  end
148
-
149
154
 
150
155
  def initialize(config = {}, parent = nil)
151
156
  super
@@ -198,6 +203,9 @@ module Netzke
198
203
  res << {:name => :ext_config__enable_extended_search, :type => :boolean} if config[:extended_search_available]
199
204
  res << {:name => :ext_config__enable_edit_in_form, :type => :boolean} if config[:edit_in_form_available]
200
205
 
206
+ # TODO: buggy thing
207
+ # res << {:name => :layout__columns, :type => :json}
208
+
201
209
  res
202
210
 
203
211
  end
@@ -248,11 +256,10 @@ module Netzke
248
256
  :add => {:text => 'Add', :disabled => ext_config[:prohibit_create]},
249
257
  :edit => {:text => 'Edit', :disabled => true},
250
258
  :del => {:text => 'Delete', :disabled => true},
251
- :apply => {:text => 'Apply', :disabled => ext_config[:prohibit_update] &&
252
- ext_config[:prohibit_create]},
259
+ :apply => {:text => 'Apply', :disabled => ext_config[:prohibit_update] && ext_config[:prohibit_create]},
253
260
  :add_in_form => {:text => 'Add in form', :disabled => !ext_config[:enable_edit_in_form]},
254
261
  :edit_in_form => {:text => 'Edit in form', :disabled => true},
255
- :search => {:text => 'Search', :disabled => !ext_config[:enable_extended_search]}
262
+ :search => {:text => 'Search', :disabled => !ext_config[:enable_extended_search], :checked => true}
256
263
  }
257
264
  end
258
265
 
@@ -262,14 +269,14 @@ module Netzke
262
269
  # Edit in form
263
270
  res.merge!({
264
271
  :add_form => {
265
- :widget_class_name => "GridPanelExtras::RecordFormWindow",
272
+ :widget_class_name => "GridPanel::RecordFormWindow",
266
273
  :ext_config => {
267
- :title => "Add #{config[:data_class_name].humanize}",
274
+ :title => "Add #{data_class.name.humanize}",
268
275
  :button_align => "right"
269
276
  },
270
277
  :item => {
271
278
  :widget_class_name => "FormPanel",
272
- :data_class_name => config[:data_class_name],
279
+ :data_class_name => data_class.name,
273
280
  :persistent_config => config[:persistent_config],
274
281
  :strong_default_attrs => config[:strong_default_attrs],
275
282
  :ext_config => {
@@ -278,13 +285,13 @@ module Netzke
278
285
  :header => false,
279
286
  :mode => ext_config[:mode]
280
287
  },
281
- :record => config[:data_class_name].constantize.new
288
+ :record => data_class.new
282
289
  }
283
290
  },
284
291
 
285
292
  :edit_form => {
286
293
  :widget_class_name => "FormPanel",
287
- :data_class_name => config[:data_class_name],
294
+ :data_class_name => data_class.name,
288
295
  :persistent_config => config[:persistent_config],
289
296
  :ext_config => {
290
297
  :bbar => false,
@@ -295,7 +302,7 @@ module Netzke
295
302
 
296
303
  :multi_edit_form => {
297
304
  :widget_class_name => "FormPanel",
298
- :data_class_name => config[:data_class_name],
305
+ :data_class_name => data_class.name,
299
306
  :persistent_config => config[:persistent_config],
300
307
  :ext_config => {
301
308
  :bbar => false,
@@ -306,7 +313,7 @@ module Netzke
306
313
 
307
314
  :new_record_form => {
308
315
  :widget_class_name => "FormPanel",
309
- :data_class_name => config[:data_class_name],
316
+ :data_class_name => data_class.name,
310
317
  :persistent_config => config[:persistent_config],
311
318
  :strong_default_attrs => config[:strong_default_attrs],
312
319
  :ext_config => {
@@ -314,7 +321,7 @@ module Netzke
314
321
  :header => false,
315
322
  :mode => ext_config[:mode]
316
323
  },
317
- :record => config[:data_class_name].constantize.new
324
+ :record => data_class.new
318
325
  }
319
326
  }) if ext_config[:enable_edit_in_form]
320
327
 
@@ -322,7 +329,7 @@ module Netzke
322
329
  res.merge!({
323
330
  :search_panel => {
324
331
  :widget_class_name => "SearchPanel",
325
- :search_class_name => config[:data_class_name],
332
+ :search_class_name => data_class.name,
326
333
  :persistent_config => config[:persistent_config],
327
334
  :ext_config => {
328
335
  :header => false,
@@ -349,8 +356,8 @@ module Netzke
349
356
 
350
357
  def get_columns
351
358
  if persistent_config_enabled?
352
- persistent_config['layout__columns'] ||= default_columns
353
- res = normalize_array_of_columns(persistent_config['layout__columns'])
359
+ columns = persistent_config['layout__columns'] || default_columns
360
+ res = normalize_array_of_columns(columns)
354
361
  else
355
362
  res = default_columns
356
363
  end
@@ -437,6 +444,8 @@ module Netzke
437
444
  c[:width] ||= 120 if c[:editor] == :xdatetime
438
445
  # hide ID column
439
446
  c[:hidden] = true if c[:name] == data_class.primary_key.to_sym && c[:hidden].nil?
447
+ # make ID column read-only
448
+ c[:editable] = false if c[:name] == data_class.primary_key.to_sym && c[:editable].nil?
440
449
 
441
450
  # Some default limitations for virtual columns
442
451
  if type == :virtual
@@ -0,0 +1,358 @@
1
+ require 'activerecord'
2
+ require 'searchlogic'
3
+ require 'will_paginate'
4
+
5
+ module Netzke
6
+ class GridPanel < Base
7
+ module GridPanelApi
8
+ def post_data(params)
9
+ success = true
10
+ mod_records = {}
11
+ [:create, :update].each do |operation|
12
+ data = ActiveSupport::JSON.decode(params["#{operation}d_records"]) if params["#{operation}d_records"]
13
+ if !data.nil? && !data.empty? # data may be nil for one of the operations
14
+ mod_records[operation] = process_data(data, operation)
15
+ mod_records[operation] = nil if mod_records[operation].empty?
16
+ end
17
+ break if !success
18
+ end
19
+ {
20
+ :update_new_records => mod_records[:create],
21
+ :update_mod_records => mod_records[:update] || {},
22
+ :feedback => @flash
23
+ }
24
+ end
25
+
26
+ def get_data(params = {})
27
+ if !ext_config[:prohibit_read]
28
+ records = get_records(params)
29
+ {:data => records.map{|r| r.to_array(normalized_columns, self)}, :total => ext_config[:enable_pagination] && records.total_entries}
30
+ else
31
+ flash :error => "You don't have permissions to read data"
32
+ {:feedback => @flash}
33
+ end
34
+ end
35
+
36
+ def delete_data(params)
37
+ if !ext_config[:prohibit_delete]
38
+ record_ids = ActiveSupport::JSON.decode(params[:records])
39
+ data_class.destroy(record_ids)
40
+ {:feedback => "Deleted #{record_ids.size} record(s)", :load_store_data => get_data}
41
+ else
42
+ {:feedback => "You don't have permissions to delete data"}
43
+ end
44
+ end
45
+
46
+ # Given an index of a column among enabled (non-excluded) columns, provides the index (position) in the table
47
+ def normalize_index(index)
48
+ norm_index = 0
49
+ index.times do
50
+ while true do
51
+ norm_index += 1
52
+ break unless normalized_columns[norm_index][:excluded]
53
+ end
54
+ end
55
+ norm_index
56
+ end
57
+
58
+ def resize_column(params)
59
+ raise "Called api_resize_column while not configured to do so" if ext_config[:enable_column_resize] == false
60
+ column_at(normalize_index(params[:index].to_i))[:width] = params[:size].to_i
61
+ save_columns!
62
+ {}
63
+ end
64
+
65
+ def hide_column(params)
66
+ raise "Called api_hide_column while not configured to do so" if ext_config[:enable_column_hide] == false
67
+ column_at(normalize_index(params[:index].to_i))[:hidden] = params[:hidden].to_b
68
+ save_columns!
69
+ {}
70
+ end
71
+
72
+ def move_column(params)
73
+ raise "Called api_move_column while not configured to do so" if ext_config[:enable_column_move] == false
74
+ remove_from = normalize_index(params[:old_index].to_i)
75
+ insert_to = normalize_index(params[:new_index].to_i)
76
+ column_to_move = columns.delete_at(remove_from)
77
+ columns.insert(insert_to, column_to_move)
78
+ save_columns!
79
+
80
+ # reorder the columns on the client side (still not sure if it's not an overkill)
81
+ # {:reorder_columns => columns.map(&:name)} # Well, I think it IS an overkill - commented out
82
+ # until proven to be necessary
83
+ {}
84
+ end
85
+
86
+ # Return the choices for the column
87
+ def get_combobox_options(params)
88
+ column = params[:column]
89
+ query = params[:query]
90
+ {:data => data_class.options_for(column, query).map{|s| [s]}}
91
+ end
92
+
93
+ # Returns searchlogic's search with all the conditions
94
+ def get_search(params)
95
+ @search ||= begin
96
+ # make params coming from Ext grid filters understandable by searchlogic
97
+ search_params = normalize_params(params)
98
+
99
+ # merge with conditions coming from the config
100
+ search_params[:conditions].deep_merge!(config[:conditions] || {})
101
+
102
+ # merge with extra conditions (in searchlogic format, come from the extended search form)
103
+ search_params[:conditions].deep_merge!(
104
+ normalize_extra_conditions(ActiveSupport::JSON.decode(params[:extra_conditions]))
105
+ ) if params[:extra_conditions]
106
+
107
+ search = data_class.search(search_params)
108
+
109
+ # applying scopes
110
+ scopes.each do |s|
111
+ if s.is_a?(Array)
112
+ scope_name, *args = s
113
+ search.send(scope_name, *args)
114
+ else
115
+ search.send(s, true)
116
+ end
117
+ end
118
+
119
+ search
120
+ end
121
+ end
122
+
123
+ def configuration_panel__columns__get_combobox_options(params)
124
+ query = params[:query]
125
+
126
+ data_arry = case params[:column]
127
+ when "name"
128
+ predefined_columns.map{ |c| c[:name].to_s }
129
+ else
130
+ raise RuntimeError, "Don't know about options for column '#{params[:column]}'"
131
+ end
132
+
133
+ {:data => data_arry.grep(/^#{query}/).map{ |n| [n] }}.to_nifty_json
134
+ end
135
+
136
+ protected
137
+
138
+ # operation => :update || :create
139
+ def process_data(data, operation)
140
+ success = true
141
+ # mod_record_ids = []
142
+ mod_records = {}
143
+ if !ext_config["prohibit_#{operation}".to_sym]
144
+ modified_records = 0
145
+ data.each do |record_hash|
146
+ id = record_hash.delete('id')
147
+ record = operation == :create ? data_class.new : data_class.find(id)
148
+ success = true
149
+
150
+ # merge with strong default attirbutes
151
+ record_hash.merge!(config[:strong_default_attrs]) if config[:strong_default_attrs]
152
+
153
+ # process all attirubutes for this record
154
+ record_hash.each_pair do |k,v|
155
+ begin
156
+ record.send("#{k}=",v)
157
+ rescue ArgumentError => exc
158
+ flash :error => exc.message
159
+ success = false
160
+ break
161
+ end
162
+ end
163
+
164
+ # try to save
165
+ # modified_records += 1 if success && record.save
166
+ mod_records[id] = record.to_array(columns, self) if success && record.save
167
+ # mod_record_ids << id if success && record.save
168
+
169
+ # flash eventual errors
170
+ if !record.errors.empty?
171
+ success = false
172
+ record.errors.each_full do |msg|
173
+ flash :error => msg
174
+ end
175
+ end
176
+ end
177
+ # flash :notice => "#{operation.to_s.capitalize}d #{modified_records} record(s)"
178
+ else
179
+ success = false
180
+ flash :error => "You don't have permissions to #{operation} data"
181
+ end
182
+ mod_records
183
+ end
184
+
185
+ # get records
186
+ def get_records(params)
187
+ # Restore params from widget_session if requested
188
+ if params[:with_last_params]
189
+ params = widget_session[:last_params]
190
+ else
191
+ # remember the last params
192
+ widget_session[:last_params] = params
193
+ end
194
+
195
+ search = get_search(params)
196
+
197
+ # sorting
198
+ if params[:sort]
199
+ assoc, method = params[:sort].split('__')
200
+ sort_string = method.nil? ? assoc : "#{assoc}_#{method}"
201
+ sort_string = (params[:dir] == "ASC" ? "ascend_by_" : "descend_by_") + sort_string
202
+ search.order(sort_string)
203
+ end
204
+
205
+ # pagination
206
+ if ext_config[:enable_pagination]
207
+ per_page = ext_config[:rows_per_page]
208
+ page = params[:limit] ? params[:start].to_i/params[:limit].to_i + 1 : 1
209
+ search.paginate(:per_page => per_page, :page => page)
210
+ else
211
+ search.all
212
+ end
213
+ end
214
+
215
+ # Create record with form
216
+ def create_new_record(params)
217
+ form_data = ActiveSupport::JSON.decode(params[:data])
218
+ res = aggregatee_instance(:new_record_form).create_or_update_record(form_data)
219
+
220
+ if res[:set_form_values]
221
+ # successful creation
222
+ res[:set_form_values] = nil
223
+ res[:on_successfull_record_creation] = true
224
+ end
225
+ res
226
+ end
227
+
228
+ # Move rows
229
+ def move_rows(params)
230
+ if defined?(ActsAsList) && data_class.ancestors.include?(ActsAsList::InstanceMethods)
231
+ ids = JSON.parse(params[:ids]).reverse
232
+ ids.each_with_index do |id, i|
233
+ r = data_class.find(id)
234
+ r.insert_at(params[:new_index].to_i + i + 1)
235
+ end
236
+ else
237
+ raise RuntimeError, "Data class should 'acts_as_list' to support moving rows"
238
+ end
239
+ {}
240
+ end
241
+
242
+ # When providing the edit_form aggregatee, fill in the form with the requested record
243
+ def load_aggregatee_with_cache(params)
244
+ if params[:id] == 'editForm'
245
+ aggregatees[:edit_form].merge!(:record_id => params[:record_id])
246
+ end
247
+
248
+ super
249
+ end
250
+
251
+ # Search scopes, in searchlogic format
252
+ def scopes
253
+ @scopes ||= config[:scopes] || []
254
+ end
255
+
256
+ # Converts Ext.grid.GridFilters filters to searchlogic conditions, e.g.
257
+ # {"0" => {
258
+ # "data" => {
259
+ # "type" => "numeric",
260
+ # "comparison" => "gt",
261
+ # "value" => 10 },
262
+ # "field" => "id"
263
+ # },
264
+ # "1" => {
265
+ # "data" => {
266
+ # "type" => "string",
267
+ # "value" => "pizza"
268
+ # },
269
+ # "field" => "food_name"
270
+ # }}
271
+ #
272
+ # =>
273
+ #
274
+ # {"id_gt" => 100, "food_name_contains" => "pizza"}
275
+ def convert_filters(column_filter)
276
+ res = {}
277
+ column_filter.each_pair do |k,v|
278
+ field = v["field"].dup
279
+ case v["data"]["type"]
280
+ when "string"
281
+ field << "_contains"
282
+ when "numeric"
283
+ field << "_#{v["data"]["comparison"]}"
284
+ end
285
+ value = v["data"]["value"]
286
+ res.merge!({field => value})
287
+ end
288
+ res
289
+ end
290
+
291
+ def normalize_extra_conditions(conditions)
292
+ conditions.deep_convert_keys{|k| k.to_s.gsub("__", "_").to_sym}
293
+ end
294
+
295
+ # make params understandable to searchlogic
296
+ def normalize_params(params)
297
+ # filters
298
+ conditions = params[:filter] && convert_filters(params[:filter])
299
+
300
+ normalized_conditions = {}
301
+ conditions && conditions.each_pair do |k, v|
302
+ assoc, method = k.split('__')
303
+ normalized_conditions.merge!(method.nil? ? {assoc => v} : {assoc => {method => v}})
304
+ end
305
+
306
+ {:conditions => normalized_conditions}
307
+ end
308
+
309
+ ## Edit in form specific API
310
+ def new_record_form__netzke_submit(params)
311
+ res = aggregatee_instance(:new_record_form).netzke_submit(params)
312
+
313
+ if res[:set_form_values]
314
+ # successful creation
315
+ res[:set_form_values] = nil
316
+ res.merge!({
317
+ :parent => {:on_successfull_record_creation => true}
318
+ })
319
+ end
320
+ res.to_nifty_json
321
+ end
322
+
323
+ def check_for_positive_result(res)
324
+ if res[:set_form_values]
325
+ # successful creation
326
+ res[:set_form_values] = nil
327
+ res.merge!({
328
+ :parent => {:on_successfull_edit => true}
329
+ })
330
+ end
331
+ end
332
+
333
+ def edit_form__netzke_submit(params)
334
+ res = aggregatee_instance(:edit_form).netzke_submit(params)
335
+
336
+ check_for_positive_result(res)
337
+
338
+ res.to_nifty_json
339
+ end
340
+
341
+ def multi_edit_form__netzke_submit(params)
342
+ ids = ActiveSupport::JSON.decode(params.delete(:ids))
343
+
344
+ res = {}
345
+ ids.each do |id|
346
+ form_instance = aggregatee_instance(:edit_form, :record => data_class.find(id))
347
+ res = form_instance.netzke_submit(params)
348
+ break if !res[:set_form_values]
349
+ end
350
+
351
+ check_for_positive_result(res)
352
+
353
+ res.to_nifty_json
354
+ end
355
+
356
+ end
357
+ end
358
+ end