netzke-basepack 0.6.3 → 0.6.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. data/CHANGELOG.rdoc +17 -0
  2. data/README.rdoc +1 -1
  3. data/Rakefile +3 -0
  4. data/TODO.rdoc +1 -4
  5. data/features/form_panel.feature +2 -1
  6. data/features/grid_panel.feature +134 -5
  7. data/features/nested_attributes.feature +4 -1
  8. data/features/paging_form_panel.feature +41 -0
  9. data/features/search_in_grid.feature +20 -7
  10. data/features/step_definitions/form_panel_steps.rb +35 -0
  11. data/features/step_definitions/generic_steps.rb +20 -1
  12. data/features/step_definitions/grid_panel_steps.rb +63 -0
  13. data/features/support/env.rb +4 -0
  14. data/features/validations_in_grid.feature +13 -0
  15. data/features/virtual_attributes.feature +5 -9
  16. data/javascripts/basepack.js +21 -650
  17. data/javascripts/datetimefield.js +24 -0
  18. data/lib/generators/netzke/basepack_generator.rb +10 -0
  19. data/{generators/netzke_basepack/templates/public_assets → lib/generators/netzke/templates/assets}/ts-checkbox.gif +0 -0
  20. data/{generators/netzke_basepack → lib/generators/netzke}/templates/create_netzke_field_lists.rb +0 -0
  21. data/lib/netzke-basepack.rb +3 -41
  22. data/lib/netzke/active_record/attributes.rb +17 -21
  23. data/lib/netzke/basepack.rb +6 -1
  24. data/lib/netzke/basepack/data_accessor.rb +166 -0
  25. data/lib/netzke/basepack/form_panel.rb +69 -20
  26. data/lib/netzke/basepack/form_panel/fields.rb +15 -17
  27. data/lib/netzke/basepack/form_panel/javascripts/comma_list_cbg.js +7 -0
  28. data/lib/netzke/basepack/form_panel/javascripts/display_mode.js +6 -0
  29. data/lib/netzke/basepack/form_panel/javascripts/{main.js → form_panel.js} +42 -8
  30. data/lib/netzke/basepack/form_panel/javascripts/n_radio_group.js +24 -7
  31. data/lib/netzke/basepack/form_panel/javascripts/readonly_mode.js +52 -0
  32. data/lib/netzke/basepack/form_panel/services.rb +28 -2
  33. data/lib/netzke/basepack/form_panel/stylesheets/readonly_mode.css +14 -0
  34. data/lib/netzke/basepack/grid_panel.rb +151 -181
  35. data/lib/netzke/basepack/grid_panel/columns.rb +122 -84
  36. data/lib/netzke/basepack/grid_panel/javascripts/advanced_search.js +22 -27
  37. data/lib/netzke/basepack/grid_panel/javascripts/{main.js → grid_panel.js} +182 -108
  38. data/lib/netzke/basepack/grid_panel/record_form_window.rb +7 -2
  39. data/lib/netzke/basepack/grid_panel/services.rb +58 -39
  40. data/lib/netzke/basepack/paging_form_panel.rb +86 -25
  41. data/lib/netzke/basepack/query_builder.rb +105 -0
  42. data/lib/netzke/basepack/query_builder/javascripts/query_builder.js +140 -0
  43. data/lib/netzke/basepack/search_panel.rb +61 -44
  44. data/lib/netzke/basepack/search_panel/javascripts/condition_field.js +153 -0
  45. data/lib/netzke/basepack/search_panel/javascripts/search_panel.js +64 -0
  46. data/lib/netzke/basepack/search_window.rb +64 -0
  47. data/lib/netzke/basepack/simple_app.rb +1 -1
  48. data/lib/netzke/basepack/simple_app/javascripts/{main.js → simple_app.js} +0 -0
  49. data/lib/netzke/basepack/tab_panel.rb +1 -2
  50. data/lib/netzke/basepack/tab_panel/javascripts/{main.js → tab_panel.js} +0 -0
  51. data/lib/netzke/basepack/version.rb +1 -1
  52. data/locales/en.yml +71 -4
  53. data/netzke-basepack.gemspec +47 -22
  54. data/spec/active_record/attributes_spec.rb +6 -6
  55. data/spec/components/form_panel_spec.rb +2 -13
  56. data/stylesheets/datetimefield.css +54 -0
  57. data/test/rails_app/Gemfile +3 -3
  58. data/test/rails_app/Gemfile.lock +67 -57
  59. data/test/rails_app/README +1 -256
  60. data/test/rails_app/app/components/book_form.rb +1 -3
  61. data/test/rails_app/app/components/book_form_with_custom_fields.rb +20 -0
  62. data/test/rails_app/app/components/book_form_with_nested_attributes.rb +18 -0
  63. data/test/rails_app/app/components/book_grid.rb +3 -1
  64. data/test/rails_app/app/components/book_grid_loader.rb +24 -0
  65. data/test/rails_app/app/components/book_grid_with_custom_columns.rb +28 -0
  66. data/test/rails_app/app/components/book_grid_with_default_values.rb +1 -1
  67. data/test/rails_app/app/components/book_grid_with_virtual_attributes.rb +0 -1
  68. data/test/rails_app/app/components/book_paging_form_panel.rb +3 -2
  69. data/test/rails_app/app/components/book_presentation.rb +3 -3
  70. data/test/rails_app/app/components/book_query_builder.rb +8 -0
  71. data/test/rails_app/app/components/book_search_panel.rb +5 -0
  72. data/test/rails_app/app/components/book_search_panel/javascripts/i18n_de.js +6 -0
  73. data/test/rails_app/app/components/double_book_grid.rb +18 -0
  74. data/test/rails_app/app/components/form_without_model.rb +1 -1
  75. data/test/rails_app/app/components/paging_form_with_search.rb +39 -0
  76. data/test/rails_app/app/components/user_grid.rb +1 -1
  77. data/test/rails_app/app/models/author.rb +1 -0
  78. data/test/rails_app/config/application.rb +6 -1
  79. data/test/rails_app/config/database.yml +7 -5
  80. data/test/rails_app/config/environments/test.rb +1 -1
  81. data/test/rails_app/config/locales/de.yml +35 -0
  82. data/test/rails_app/config/locales/es.yml +84 -8
  83. data/test/rails_app/db/migrate/20101026190021_create_books.rb +2 -2
  84. data/test/rails_app/db/migrate/20110213213050_create_netzke_component_states.rb +20 -0
  85. data/test/rails_app/db/schema.rb +2 -18
  86. data/test/rails_app/public/netzke/basepack/ts-checkbox.gif +0 -0
  87. data/test/unit/active_record_basepack_test.rb +1 -1
  88. metadata +46 -45
  89. data/generators/netzke_basepack/netzke_basepack_generator.rb +0 -13
  90. data/lib/netzke/basepack/grid_panel/search_window.rb +0 -56
  91. data/lib/netzke/data_accessor.rb +0 -113
  92. data/lib/netzke/ext.rb +0 -7
  93. data/lib/netzke/fields_configurator.rb +0 -170
  94. data/lib/netzke/json_array_editor.rb +0 -67
  95. data/lib/netzke/masquerade_selector.rb +0 -53
  96. data/test/rails_app/app/components/some_search_panel.rb +0 -34
  97. data/test/rails_app/db/development_structure.sql +0 -93
  98. data/test/rails_app/db/migrate/20100905214933_create_netzke_preferences.rb +0 -16
@@ -3,67 +3,97 @@ module Netzke
3
3
  class GridPanel < Netzke::Base
4
4
  module Columns
5
5
  extend ActiveSupport::Concern
6
+ extend ActiveSupport::Memoizable
7
+
8
+ # module ClassMethods
9
+ # # Columns to be displayed by the FieldConfigurator, "meta-columns". Each corresponds to a configuration
10
+ # # option for each column in the grid.
11
+ # def meta_columns
12
+ # [
13
+ # # Whether the column will be present in the grid, also in :hidden or :meta state. The value for this column will
14
+ # # always be sent to/from the JS grid to the server
15
+ # {:name => "included", :attr_type => :boolean, :width => 40, :header => "Incl", :default_value => true},
16
+ #
17
+ # # The name of the column. May be any accessible method or attribute of the data_class.
18
+ # {:name => "name", :attr_type => :string, :width => 200},
19
+ #
20
+ # # The header for the column.
21
+ # {:name => "label", :attr_type => :string, :width => 200, :header => "Header"},
22
+ #
23
+ # # The default value of this column. Is used when a new row in the grid gets created.
24
+ # {:name => "default_value", :attr_type => :string, :width => 200},
25
+ #
26
+ # # Options for drop-downs
27
+ # {:name => "combobox_options", :attr_type => :string, :editor => :textarea, :width => 200},
28
+ #
29
+ # # Whether the column is editable in the grid.
30
+ # {:name => "read_only", :attr_type => :boolean, :header => "R/O", :tooltip => "Read-only"},
31
+ #
32
+ # # Whether the column will be in the hidden state (hide/show columns from the column menu, if it's enabled).
33
+ # {:name => "hidden", :attr_type => :boolean},
34
+ #
35
+ # # Whether the column should have "grid filters" enabled
36
+ # # (see here: http://www.extjs.com/deploy/dev/examples/grid-filtering/grid-filter-local.html)
37
+ # {:name => "with_filters", :attr_type => :boolean, :default_value => true, :header => "Filters"},
38
+ #
39
+ # #
40
+ # # Below some rarely used parameters, hidden by default (you can always un-hide them from the column menu).
41
+ # #
42
+ #
43
+ # # The column's width
44
+ # {:name => "width", :attr_type => :integer, :hidden => true},
45
+ #
46
+ # # Whether the column should be hideable
47
+ # {:name => "hideable", :attr_type => :boolean, :default_value => true, :hidden => true},
48
+ #
49
+ # # Whether the column should be sortable (why change it? normally it's hardcoded)
50
+ # {:name => "sortable", :attr_type => :boolean, :default_value => true, :hidden => true},
51
+ # ]
52
+ # end
53
+ #
54
+ # end
6
55
 
7
- module ClassMethods
8
- # Columns to be displayed by the FieldConfigurator, "meta-columns". Each corresponds to a configuration
9
- # option for each column in the grid.
10
- def meta_columns
11
- [
12
- # Whether the column will be present in the grid, also in :hidden or :meta state. The value for this column will
13
- # always be sent to/from the JS grid to the server
14
- {:name => "included", :attr_type => :boolean, :width => 40, :header => "Incl", :default_value => true},
15
-
16
- # The name of the column. May be any accessible method or attribute of the data_class.
17
- {:name => "name", :attr_type => :string, :width => 200},
18
-
19
- # The header for the column.
20
- {:name => "label", :attr_type => :string, :width => 200, :header => "Header"},
21
-
22
- # The default value of this column. Is used when a new row in the grid gets created.
23
- {:name => "default_value", :attr_type => :string, :width => 200},
24
-
25
- # Options for drop-downs
26
- {:name => "combobox_options", :attr_type => :string, :editor => :textarea, :width => 200},
27
-
28
- # Whether the column is editable in the grid.
29
- {:name => "read_only", :attr_type => :boolean, :header => "R/O", :tooltip => "Read-only"},
30
-
31
- # Whether the column will be in the hidden state (hide/show columns from the column menu, if it's enabled).
32
- {:name => "hidden", :attr_type => :boolean},
33
-
34
- # Whether the column should have "grid filters" enabled
35
- # (see here: http://www.extjs.com/deploy/dev/examples/grid-filtering/grid-filter-local.html)
36
- {:name => "with_filters", :attr_type => :boolean, :default_value => true, :header => "Filters"},
37
-
38
- #
39
- # Below some rarely used parameters, hidden by default (you can always un-hide them from the column menu).
40
- #
56
+ # Normalized columns for the grid, e.g.:
57
+ # [{:name => :id, :hidden => true, ...}, {:name => :name, :editable => false, ...}, ...]
58
+ # Possible options:
59
+ # * +with_excluded+ - when set to true, also excluded columns will be returned (handy for dynamic column configuration)
60
+ # * +with_meta+ - when set to true, the meta column will be included as the last column
61
+ def columns(options = {})
62
+ [].tap do |cols|
63
+ if loaded_columns = load_columns
64
+ filter_out_excluded_columns(loaded_columns) unless options[:with_excluded]
65
+ cols.concat(reverse_merge_equally_named_columns(loaded_columns, initial_columns(options[:with_excluded])))
66
+ else
67
+ cols.concat(initial_columns(options[:with_excluded]))
68
+ end
41
69
 
42
- # The column's width
43
- {:name => "width", :attr_type => :integer, :hidden => true},
70
+ append_meta_column(cols) if options[:with_meta]
71
+ end
72
+ end
44
73
 
45
- # Whether the column should be hideable
46
- {:name => "hideable", :attr_type => :boolean, :default_value => true, :hidden => true},
74
+ memoize :columns
47
75
 
48
- # Whether the column should be sortable (why change it? normally it's hardcoded)
49
- {:name => "sortable", :attr_type => :boolean, :default_value => true, :hidden => true},
50
- ]
76
+ def append_meta_column(cols)
77
+ cols << {}.tap do |c|
78
+ c.merge!(
79
+ :name => "_meta",
80
+ :meta => true,
81
+ :getter => lambda do |r|
82
+ meta_data(r)
83
+ end
84
+ )
85
+ c[:default_value] = meta_default_data if meta_default_data.present?
51
86
  end
87
+ end
52
88
 
89
+ # default_value for the meta column; used when a new record is being created in the grid
90
+ def meta_default_data
91
+ get_default_association_values.present? ? { :association_values => get_default_association_values.literalize_keys } : {}
53
92
  end
54
93
 
55
- # Normalized columns for the grid, e.g.:
56
- # [{:name => :id, :hidden => true, ...}, {:name => :name, :editable => false, ...}, ...]
57
- def columns(only_included = true)
58
- @columns ||= begin
59
- if cols = load_columns
60
- filter_out_excluded_columns(cols) if only_included
61
- reverse_merge_equally_named_columns(cols, initial_columns)
62
- cols
63
- else
64
- initial_columns(only_included)
65
- end
66
- end
94
+ # Override it when you need extra meta data to be passed through the meta column
95
+ def meta_data(r)
96
+ { :association_values => get_association_values(r).literalize_keys }
67
97
  end
68
98
 
69
99
  # Columns as a hash, for easier access to a specific column
@@ -80,7 +110,7 @@ module Netzke
80
110
  end
81
111
 
82
112
  # Columns that represent a smart merge of default_columns and columns passed during the configuration.
83
- def initial_columns(only_included = true)
113
+ def initial_columns(with_excluded = false)
84
114
  # Normalize here, as from the config we can get symbols (names) instead of hashes
85
115
  columns_from_config = config[:columns] && normalize_attrs(config[:columns])
86
116
 
@@ -101,7 +131,7 @@ module Netzke
101
131
  columns_for_create = default_columns
102
132
  end
103
133
 
104
- filter_out_excluded_columns(columns_for_create) if only_included
134
+ filter_out_excluded_columns(columns_for_create) unless with_excluded
105
135
 
106
136
  # Make the column config complete with the defaults
107
137
  columns_for_create.each do |c|
@@ -143,7 +173,7 @@ module Netzke
143
173
  end
144
174
 
145
175
  def set_default_editor(c)
146
- c[:editor] ||= editor_for_attr_type(c[:attr_type])
176
+ # c[:editor] ||= editor_for_attr_type(c[:attr_type]) # This is done in JS!
147
177
  c[:editor] = {:xtype => c[:editor]} if c[:editor].is_a?(Symbol)
148
178
  end
149
179
 
@@ -157,19 +187,21 @@ module Netzke
157
187
  end
158
188
 
159
189
  def set_default_editable(c)
160
- not_editable_if = primary_key_attr?(c)
161
- not_editable_if ||= c[:virtual]
162
- not_editable_if ||= c.delete(:read_only)
190
+ if c[:editable].nil?
191
+ not_editable_if = primary_key_attr?(c)
192
+ not_editable_if ||= c[:virtual] && !association_attr?(c[:name])
193
+ not_editable_if ||= c.delete(:read_only)
163
194
 
164
- editable_if = data_class.column_names.include?(c[:name])
165
- editable_if ||= data_class.instance_methods.map(&:to_s).include?("#{c[:name]}=")
166
- editable_if ||= association_attr?(c[:name])
195
+ editable_if = data_class.column_names.include?(c[:name])
196
+ editable_if ||= data_class.instance_methods.map(&:to_s).include?("#{c[:name]}=")
197
+ editable_if ||= association_attr?(c[:name])
167
198
 
168
- c[:editable] = editable_if && !not_editable_if if c[:editable].nil?
199
+ c[:editable] = editable_if && !not_editable_if
200
+ end
169
201
  end
170
202
 
171
203
  def set_default_sortable(c)
172
- c[:sortable] = !c[:virtual] if c[:sortable].nil?
204
+ c[:sortable] = !c[:virtual] if c[:sortable].nil? # TODO: optimize - don't set it to false
173
205
  end
174
206
 
175
207
  def set_default_filterable(c)
@@ -182,7 +214,7 @@ module Netzke
182
214
  end
183
215
 
184
216
  def editor_for_association
185
- :combobox
217
+ :netzkeremotecombo
186
218
  end
187
219
 
188
220
  # Returns a hash that maps a column type to the editor xtype. Override if you want different editors.
@@ -191,7 +223,7 @@ module Netzke
191
223
  :integer => :numberfield,
192
224
  :boolean => :checkbox,
193
225
  :date => :datefield,
194
- :datetime => :xdatetime,
226
+ :datetime => :datetimefield,
195
227
  :text => :textarea,
196
228
  :string => :textfield
197
229
  }
@@ -199,7 +231,7 @@ module Netzke
199
231
 
200
232
  # Detects an association column and sets up the proper editor.
201
233
  def detect_association(c)
202
- assoc, assoc_method = get_assoc_and_method(c)
234
+ assoc, assoc_method = assoc_and_assoc_method_for_column(c)
203
235
  if assoc
204
236
  assoc_column = assoc.klass.columns_hash[assoc_method]
205
237
  assoc_method_type = assoc_column.try(:type)
@@ -209,20 +241,11 @@ module Netzke
209
241
  c[:editor] ||= editor_for_attr_type(assoc_method_type)
210
242
  else
211
243
  c[:editor] ||= assoc_method_type == :boolean ? editor_for_attr_type(:boolean) : editor_for_association
244
+ c[:assoc] = true
212
245
  end
213
246
  end
214
247
  end
215
248
 
216
- def get_assoc_and_method(c)
217
- if c[:name].index("__")
218
- assoc_name, assoc_method = c[:name].split('__')
219
- assoc = data_class.reflect_on_association(assoc_name.to_sym)
220
- [assoc, assoc_method]
221
- else
222
- [nil, nil]
223
- end
224
- end
225
-
226
249
  # Default fields that will be displayed in the Add/Edit/Search forms
227
250
  # When overriding this method, keep in mind that the fields inside the layout must be expanded (each field represented by a hash, not just a symbol)
228
251
  def default_fields_for_forms
@@ -239,20 +262,35 @@ module Netzke
239
262
  field_config = {:name => c[:name]}
240
263
 
241
264
  # scopes for combobox options
242
- field_config[:scopes] = c[:editor].is_a?(Hash) && c[:editor][:scopes]
265
+ field_config[:scopes] = c[:editor][:scopes] if c[:editor].is_a?(Hash)
243
266
 
244
267
  field_config
245
268
  end
246
269
  end
247
270
 
248
271
  # default_fields_for_forms extended with default values (for new-record form)
249
- def default_fields_for_forms_with_default_values
250
- res = default_fields_for_forms.dup
251
- each_attr_in(res) do |a|
252
- attr_name = a[:name].to_sym
253
- a[:value] = a[:default_value] || columns_hash[attr_name].try(:fetch, :default_value, nil) || data_class.netzke_attribute_hash[attr_name].try(:fetch, :default_value, nil)
272
+ # def default_fields_for_forms_with_default_values
273
+ # res = default_fields_for_forms.dup
274
+ # each_attr_in(res) do |a|
275
+ # attr_name = a[:name].to_sym
276
+ # a[:value] = a[:default_value] || columns_hash[attr_name].try(:fetch, :default_value, nil) || data_class.netzke_attribute_hash[attr_name].try(:fetch, :default_value, nil)
277
+ # end
278
+ # res
279
+ # end
280
+
281
+ def columns_default_values
282
+ columns.inject({}) do |r,c|
283
+ assoc, assoc_method = assoc_and_assoc_method_for_column(c)
284
+ if c[:default_value].nil?
285
+ r
286
+ else
287
+ if assoc
288
+ r.merge(assoc.options[:foreign_key] || assoc.name.to_s.foreign_key => c[:default_value])
289
+ else
290
+ r.merge(c[:name] => c[:default_value])
291
+ end
292
+ end
254
293
  end
255
- res
256
294
  end
257
295
 
258
296
  # Recursively traversess items (an array) and yields each found field (a hash with :name set)
@@ -1,34 +1,29 @@
1
1
  {
2
2
  onSearch: function(el){
3
- el.toggle(el.toggled); // do not toggle immediately
4
-
5
- this.loadComponent({name: 'search_form', callback: function(win){
6
- var currentConditionsString = this.getStore().baseParams.extra_conditions;
7
- if (currentConditionsString) {
8
- win.items.first().getForm().setValues(Ext.decode(currentConditionsString));
9
- }
3
+ if (this.searchWindow) {
4
+ this.searchWindow.show();
5
+ } else {
6
+ this.loadComponent({name: 'search_form', callback: function(win){
7
+ this.searchWindow = win;
8
+ var currentConditionsString = this.getStore().baseParams.extra_conditions;
9
+ if (currentConditionsString) {
10
+ win.items.first().getForm().setValues(Ext.decode(currentConditionsString));
11
+ }
10
12
 
11
- win.items.first().on('apply', function(){
12
- win.onSearch();
13
- return false; // do not propagate the 'apply' event
14
- }, this);
13
+ win.items.first().on('apply', function(){
14
+ win.onSearch();
15
+ return false; // do not propagate the 'apply' event
16
+ }, this);
15
17
 
16
- win.on('close', function(){
17
- if (win.closeRes == 'OK'){
18
- var searchConditions = win.conditions;
19
- var filtered = false;
20
- // check if there's any search condition set
21
- for (var k in searchConditions) {
22
- if (searchConditions[k].length > 0) {
23
- filtered = true;
24
- break;
25
- }
18
+ win.on('hide', function(){
19
+ var query = win.getQuery();
20
+ if (win.closeRes == 'search'){
21
+ this.getStore().baseParams.query = Ext.encode(query);
22
+ this.getStore().load();
26
23
  }
27
- el.toggle(filtered); // toggle based on the state
28
- this.getStore().baseParams.extra_conditions = Ext.encode(win.conditions);
29
- this.getStore().load();
30
- }
31
- }, this);
32
- }, scope: this});
24
+ el.toggle(query.length > 0); // toggle based on the state
25
+ }, this);
26
+ }, scope: this});
27
+ }
33
28
  }
34
29
  }
@@ -10,11 +10,15 @@
10
10
  this.plugins = []; // checkbox colums is a special case, being a plugin
11
11
 
12
12
  var filters = [];
13
+ var metaColumn;
13
14
 
14
15
  // Run through columns and set up different configuration for each
15
16
  Ext.each(this.columns, function(c, i){
16
17
  // We will not use meta columns as actual columns (not even hidden) - only to create the records
17
- if (c.meta) return;
18
+ if (c.meta) {
19
+ metaColumn = c;
20
+ return;
21
+ }
18
22
 
19
23
  // Apply default column config
20
24
  Ext.applyIf(c, this.defaultColumnConfig);
@@ -25,13 +29,14 @@
25
29
  // Automatically calculated default values
26
30
  if (!c.header) {c.header = c.label || c.name.humanize()}
27
31
 
28
- // normalize editor
29
- if (c.editor) {
30
- c.editor = Ext.isObject(c.editor) ? c.editor : {xtype:c.editor};
31
- } else {
32
- c.editor = {xtype: this.attrTypeEditorMap[c.attrType] || 'textfield'}
32
+ // Set initial association values
33
+ if (this.inlineData) {
34
+ this.associationValues = this.inlineData.setAssociationValues;
33
35
  }
34
36
 
37
+ // normalize editor
38
+ this.normalizeEditor(c);
39
+
35
40
  // if comboboxOptions are provided, we render a combobox instead of textfield
36
41
  if (c.comboboxOptions && c.editor.xtype === "textfield") {
37
42
  c.editor = {xtype: "combobox", options: c.comboboxOptions.split('\\n')}
@@ -39,7 +44,7 @@
39
44
 
40
45
  // collect filters
41
46
  if (c.filterable){
42
- filters.push({type:this.filterTypeForAttrType(c.attrType), dataIndex:c.name});
47
+ filters.push({type: this.filterTypeForAttrType(c.attrType), dataIndex: c.name});
43
48
  }
44
49
 
45
50
  if (c.editor && c.editor.xtype == 'checkbox') {
@@ -51,38 +56,39 @@
51
56
  // a "normal" column, not a plugin
52
57
  if (!c.readOnly && !this.prohibitUpdate) {
53
58
  // c.editor contains complete config of the editor
54
- var xtype = c.editor.xtype;
55
59
  c.editor = Ext.apply({
56
60
  parentId: this.id,
57
61
  name: c.name,
58
- selectOnFocus:true
62
+ selectOnFocus: true
59
63
  }, c.editor);
60
64
  } else {
61
65
  c.editor = null;
62
66
  }
63
67
 
64
- // Normalize the renderer
65
68
  this.normalizeRenderer(c);
66
69
 
67
- // set the renderer
68
- // if (c.renderer && !Ext.isArray(c.renderer) && c.renderer.match(/^\\s*function\\s*\\(/)) {
69
- // // if the renderer is an inline function - eval it (double escaping because we are inside of the Ruby string here...)
70
- // eval("c.renderer = " + c.renderer + ";");
71
- // } else if (Ext.isFunction(this[c.renderer])) {
72
- // // whether the renderer is defined in this.scope
73
- // c.renderer = this[c.renderer].createDelegate(this);
74
- // } else {
75
- // // othrewise it's a string representing the name of the renderer or a json-encoded array,
76
- // // where the first parameter is the renderer's name, and the rest - parameters that should be
77
- // // passed to the renderer at the moment of calling
78
- // var renderer = Ext.netzke.normalizedRenderer(c.renderer);
79
- // if (renderer != null) {
80
- // c.renderer = renderer
81
- // };
82
- // }
83
- //
84
- // add to the list
85
- // cmConfig.push(c);
70
+ this.setDefaultColumnType(c);
71
+
72
+ // Set rendeder for association columns (the one displaying associations by the specified method instead of id)
73
+ if (c.assoc) {
74
+ c.scope = this;
75
+ var passedRenderer = c.renderer; // renderer we got from normalizeRenderer
76
+ c.renderer = function(value, a, r, ri, ci){
77
+ var editor = this.getColumnModel().getColumnAt(ci).getEditor();
78
+ var recordFromStore = editor && editor.getStore && editor.getStore().getById(value);
79
+ var renderedValue;
80
+ if (recordFromStore) {
81
+ renderedValue = recordFromStore.get('field2');
82
+ } else if (c.assoc && r.get('_meta')) {
83
+ renderedValue = r.get('_meta').associationValues[c.name] || value;
84
+ } else {
85
+ renderedValue = value;
86
+ }
87
+
88
+ return passedRenderer ? passedRenderer.call(this, renderedValue) : renderedValue;
89
+ };
90
+ }
91
+
86
92
  }
87
93
 
88
94
  }, this);
@@ -90,15 +96,47 @@
90
96
  /* ... and done with columns */
91
97
 
92
98
  // Filters
99
+ this.gridFilters = new Ext.ux.grid.GridFilters({filters:filters, encode: true});
93
100
  if (this.enableColumnFilters) {
94
- this.plugins.push(new Ext.ux.grid.GridFilters({filters:filters}));
101
+ this.plugins.push(this.gridFilters);
95
102
  }
96
103
 
97
104
  // Create Ext.data.Record constructor specific for our particular column configuration
98
105
  this.recordConfig = [];
99
- Ext.each(this.columns, function(column){this.recordConfig.push({name:column.name, defaultValue:column.defaultValue});}, this);
106
+ Ext.each(this.columns, function(column){
107
+ var extraConfig = {};
108
+ if (column.attrType == 'datetime') {
109
+ extraConfig.type = 'date';
110
+ extraConfig.dateFormat = 'Y-m-d g:i:s';
111
+ };
112
+ this.recordConfig.push(Ext.apply({name: column.name, defaultValue: column.defaultValue}, extraConfig));
113
+ }, this);
100
114
  this.Row = Ext.data.Record.create(this.recordConfig);
101
115
 
116
+ // After we created the record (model), we can get rid of the meta column
117
+ this.columns.remove(metaColumn);
118
+
119
+ // Prepare column model config with columns in the correct order
120
+ var colModelConfig = [];
121
+
122
+ Ext.each(this.columnsOrder, function(c) {
123
+ var mainColConfig;
124
+ Ext.each(this.columns, function(oc) {
125
+ if (c.name === oc.name) {
126
+ mainColConfig = oc;
127
+ return false;
128
+ }
129
+ });
130
+
131
+ colModelConfig.push(Ext.apply(mainColConfig, c));
132
+ }, this);
133
+
134
+ // We don't need original columns any longer
135
+ delete this.columns;
136
+
137
+ // ... instead - define a custom column model
138
+ this.colModel = new Ext.grid.ColumnModel(colModelConfig);
139
+
102
140
  // Drag'n'Drop
103
141
  if (this.enableRowsReordering){
104
142
  this.ddPlugin = new Ext.ux.dd.GridDragDropRowOrder({
@@ -107,74 +145,45 @@
107
145
  this.plugins.push(this.ddPlugin);
108
146
  }
109
147
 
110
- // Explicitely create the connection to get grid's data,
111
- // because we don't want the app-wide Ext.Ajax to be used,
112
- // as we are going to subscribe to its events
113
- var connection = new Ext.data.Connection({
114
- url: this.endpointUrl("get_data"),
115
- extraParams: {
116
- authenticity_token : Netzke.authenticityToken
117
- },
118
-
119
- // inform Ext.Ajax about our events
120
- listeners: {
121
- beforerequest: function(){
122
- Ext.Ajax.fireEvent('beforerequest', arguments);
123
- },
124
- requestexception: function(){
125
- Ext.Ajax.fireEvent('requestexception', arguments);
126
- },
127
- requestcomplete: function(){
128
- Ext.Ajax.fireEvent('requestcomplete', arguments);
129
- }
130
- }
131
- });
148
+ // DirectProxy that uses our Ext.direct provider
149
+ this.proxy = new Ext.data.DirectProxy({directFn: Netzke.providers[this.id].getData});
132
150
 
133
- // besides getting data into the store, we may also get commands to execute
134
- connection.on('requestcomplete', function(conn, r){
135
- var response = Ext.decode(r.responseText);
151
+ this.proxy.on('load', function (self, t, options) {
152
+ // besides getting data into the store, we may also get commands to execute
153
+ var response = t.result;
136
154
 
137
155
  // delete data-related properties
138
156
  Ext.each(['data', 'total', 'success'], function(property){delete response[property];});
139
157
  this.bulkExecute(response);
140
158
  }, this);
141
159
 
142
- // HttpProxy that uses our custom connection
143
- var httpProxy = new Ext.data.HttpProxy(connection);
144
-
145
160
  // Data store
146
- this.store = new Ext.data.Store({
147
- pruneModifiedRecords: true,
148
- proxy: this.proxy = httpProxy,
149
- reader: new Ext.data.ArrayReader({root: "data", totalProperty: "total", successProperty: "success", id:0}, this.Row),
150
- remoteSort: true,
151
- listeners:{'loadexception':{
152
- fn:this.loadExceptionHandler,
153
- scope:this
154
- }}
155
- });
161
+ this.store = this.buildStore();
156
162
 
157
163
  // Normalize bottom bar
158
- this.bbar = (this.enablePagination) ? new Ext.PagingToolbar({
164
+ this.bbar = (this.enablePagination) ? new Ext.PagingToolbar(Ext.copyTo({
159
165
  pageSize : this.rowsPerPage,
160
166
  items : this.bbar ? ["-"].concat(this.bbar) : [],
161
167
  store : this.store,
162
- emptyMsg: 'Empty',
163
- displayInfo: true
164
- }) : this.bbar;
168
+ emptyMsg: this.i18n.empty,
169
+ displayInfo: true,
170
+ plugins: this.gridFilters ? [this.gridFilters] : []
171
+ }, this.i18n, 'emptyMsg,firstText,prevText,nextText,lastText,beforePageText,afterPageText,refreshText,displayMsg')) : this.bbar;
165
172
 
166
173
  // Selection model
167
174
  if (!this.sm) this.sm = new Ext.grid.RowSelectionModel();
168
175
 
169
- // Now let Ext.grid.EditorGridPanel do the rest
170
- // Original initComponent
176
+ this.view = this.buildView();
177
+
178
+ // Now let Ext.grid.EditorGridPanel do the rest (original initComponent)
171
179
  Netzke.classes.Basepack.GridPanel.superclass.initComponent.call(this);
172
180
 
173
- // Hidden change event
174
- if (this.persistentConfig) {this.getColumnModel().on('hiddenchange', this.onColumnHiddenChange, this);}
181
+ // Persistence-related events
182
+ if (this.persistence) {
183
+ // Hidden change event
184
+ this.getColumnModel().on('hiddenchange', this.onColumnHiddenChange, this);
175
185
 
176
- // Inform the server part about column operations
177
- if (this.persistentConfig) {
186
+ // Inform the server part about column operations
178
187
  this.on('columnresize', this.onColumnResize, this);
179
188
  this.on('columnmove', this.onColumnMove, this);
180
189
  }
@@ -210,9 +219,9 @@
210
219
  this.getStore().lastOptions = {params:{limit:this.rowsPerPage, start:0}}; // this is how PagingToolbar does it...
211
220
  }
212
221
 
213
- // inlineData may also contain commands (TODO: make it DRY)
222
+ // inlineData may also contain commands (TODO: make it DRY, as this code repeats in multiple places...)
214
223
  // delete data-related properties
215
- Ext.each(['data', 'total', 'success'], function(property){delete this.inlineData[property];}, this);
224
+ Ext.each(['data', 'total', 'success'], function(property){ delete this.inlineData[property]; }, this);
216
225
  this.bulkExecute(this.inlineData);
217
226
  }
218
227
 
@@ -230,6 +239,33 @@
230
239
 
231
240
  // GridView
232
241
  this.getView().getRowClass = this.defaultGetRowClass;
242
+
243
+ // When starting editing as assocition column, pre-load the combobox store from the meta column, so that we don't see the real value of this cell (the id of the associated record), but rather the associated record by the configured method.
244
+ this.on('beforeedit', function(e){
245
+ var column = this.getColumnModel().getColumnById(this.getColumnModel().getColumnId(e.column));
246
+ if (column.assoc && column.getEditor().isXType('combo') && e.record.get('_meta')) {
247
+ column.getEditor().getStore().loadData({
248
+ data: [[e.record.get(e.field), e.record.get('_meta').associationValues[e.field]]]
249
+ });
250
+ }
251
+ }, this);
252
+ },
253
+
254
+ buildStore: function() {
255
+ return new Ext.data.Store({
256
+ pruneModifiedRecords: true,
257
+ proxy: this.proxy,
258
+ reader: new Ext.data.ArrayReader({root: "data", totalProperty: "total", successProperty: "success", id:0}, this.Row),
259
+ remoteSort: true,
260
+ listeners:{'loadexception':{
261
+ fn:this.loadExceptionHandler,
262
+ scope:this
263
+ }}
264
+ });
265
+ },
266
+
267
+ buildView: function() {
268
+ return null;
233
269
  },
234
270
 
235
271
  filterTypeForAttrType: function(attrType){
@@ -238,9 +274,9 @@
238
274
  decimal :'Numeric',
239
275
  datetime:'Date',
240
276
  date :'Date',
241
- string :'String'
277
+ string :'String',
278
+ 'boolean': 'Boolean'
242
279
  };
243
- map['boolean'] = "Boolean"; // "boolean" is a JS reserved word
244
280
  return map[attrType] || 'String';
245
281
  },
246
282
 
@@ -249,11 +285,15 @@
249
285
  "float" : "numberfield",
250
286
  "boolean": "checkbox",
251
287
  decimal : "numberfield",
252
- datetime : "xdatetime",
288
+ datetime : "datetimefield",
253
289
  date : "datefield",
254
290
  string : "textfield"
255
291
  },
256
292
 
293
+ setAssociationValues: function(assocObj) {
294
+ this.associationValues = assocObj;
295
+ },
296
+
257
297
  onAdd: function(){
258
298
  var r = new this.Row();
259
299
  r.isNew = true; // to distinguish new records
@@ -270,7 +310,7 @@
270
310
  },
271
311
 
272
312
  onDel: function() {
273
- Ext.Msg.confirm(this.i18n.confirm, this.i18n.areYouSure, function(btn){
313
+ Ext.Msg.confirm(this.i18n.confirmation, this.i18n.areYouSure, function(btn){
274
314
  if (btn == 'yes') {
275
315
  var records = [];
276
316
  this.getSelectionModel().each(function(r){
@@ -492,14 +532,26 @@
492
532
  },
493
533
 
494
534
  defaultGetRowClass: function(r){
495
- return r.isNew ? "grid-dirty-record" : ""
496
- },
535
+ return r.isNew ? "grid-dirty-record" : ""
536
+ },
497
537
 
498
538
  selectFirstRow: function(){
499
- this.getSelectionModel().suspendEvents();
500
- this.getSelectionModel().selectRow(0);
501
- this.getSelectionModel().resumeEvents();
502
- },
539
+ this.getSelectionModel().suspendEvents();
540
+ this.getSelectionModel().selectRow(0);
541
+ this.getSelectionModel().resumeEvents();
542
+ },
543
+
544
+ setDefaultColumnType: function(c) {
545
+ if (c.xtype || c.renderer) return;
546
+
547
+ switch (c.attrType) {
548
+ case 'datetime': {
549
+ c.xtype = 'datecolumn';
550
+ c.format = c.format || "Y-m-d g:i:s";
551
+ break;
552
+ }
553
+ }
554
+ },
503
555
 
504
556
  // Normalizes the renderer for a column.
505
557
  // Renderer may be:
@@ -517,34 +569,56 @@
517
569
  // * ["Some.scope.Format.customRenderer", 10, 20, 30] (if Some.scope.Format.customRenderer is a function)
518
570
  // * "function(v){ return 'Value: ' + v; }"
519
571
  normalizeRenderer: function(c) {
520
- if (!c.renderer) return;
572
+ if (!c.renderer) return;
521
573
 
522
- var name, args = [];
574
+ var name, args = [];
523
575
 
524
- if ('string' === typeof c.renderer) {
525
- name = c.renderer;
526
- } else {
527
- name = c.renderer[0];
528
- args = c.renderer.slice(1);
529
- }
576
+ if ('string' === typeof c.renderer) {
577
+ name = c.renderer;
578
+ } else {
579
+ name = c.renderer[0];
580
+ args = c.renderer.slice(1);
581
+ }
582
+
583
+ // First check whether Ext.util.Format has it
584
+ if (Ext.isFunction(Ext.util.Format[name])) {
585
+ c.renderer = Ext.util.Format[name].createDelegate(this, args, 1);
586
+ } else if (Ext.isFunction(this[name])) {
587
+ // ... then if our own class has it
588
+ c.renderer = this[name].createDelegate(this, args, 1);
589
+ } else {
590
+ // ... and, as last resort, evaluate it (allows passing inline javascript function as renderer)
591
+ eval("c.renderer = " + c.renderer + ";");
592
+ }
593
+ },
530
594
 
531
- // First check whether Ext.util.Format has it
532
- if (Ext.isFunction(Ext.util.Format[name])) {
533
- c.renderer = Ext.util.Format[name].createDelegate(this, args, 1);
534
- } else if (Ext.isFunction(this[name])) {
535
- // ... then if our own class has it
536
- c.renderer = this[name].createDelegate(this, args, 1);
595
+ normalizeEditor: function(c) {
596
+ if (c.assoc) {
597
+ } else {
598
+ if (c.editor) {
599
+ c.editor = Ext.isObject(c.editor) ? c.editor : {xtype:c.editor};
537
600
  } else {
538
- // ... and, as last resort, evaluate it (allows passing inline javascript function as renderer)
539
- eval("c.renderer = " + c.renderer + ";");
601
+ c.editor = {xtype: this.attrTypeEditorMap[c.attrType] || 'textfield'}
540
602
  }
541
- },
603
+ }
604
+
605
+ },
542
606
 
543
607
  onEdit: function(){
544
608
  var row = this.getSelectionModel().getSelected();
545
609
  if (row){
546
610
  this.tryStartEditing(this.store.indexOf(row));
547
611
  }
612
+ },
613
+
614
+ // Not a very clean approach to clean-up. The problem is that this way the advanced search functionality stops being really pluggable. With Ext JS 4 find the way to make it truely so.
615
+ onDestroy: function(){
616
+ Netzke.classes.Basepack.GridPanel.superclass.onDestroy.call(this);
617
+
618
+ // Destroy the search window (here's the problem: we are not supposed to know it exists)
619
+ if (this.searchWindow) {
620
+ this.searchWindow.destroy();
621
+ }
548
622
  }
549
623
 
550
624
  // :reorder_columns => <<-END_OF_JAVASCRIPT.l,