netzke-basepack 0.6.3 → 0.6.4

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 (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
@@ -22,7 +22,7 @@ module Netzke
22
22
 
23
23
  # Hash of fully configured fields, that are referenced in the items. E.g.:
24
24
  # {
25
- # :role__name => {:xtype => 'combobox', :disabled => true, :value => "admin"},
25
+ # :role__name => {:xtype => 'netzkeremotecombo', :disabled => true, :value => "admin"},
26
26
  # :created_at => {:xtype => 'datetime', :disabled => true, :value => "2010-10-10 10:10"}
27
27
  # }
28
28
  def fields
@@ -52,7 +52,7 @@ module Netzke
52
52
 
53
53
  # Hash of normalized field configs extracted from :items, e.g.:
54
54
  #
55
- # {:role__name => {:xtype => "combobox"}, :password => {:xtype => "passwordfield"}}
55
+ # {:role__name => {:xtype => "netzkeremotecombo"}, :password => {:xtype => "passwordfield"}}
56
56
  def fields_from_config
57
57
  items if @fields_from_config.nil? # by calling +items+ we initiate building of @fields_from_config
58
58
  @fields_from_config ||= {}
@@ -64,7 +64,7 @@ module Netzke
64
64
  def meta_columns
65
65
  [
66
66
  {:name => "included", :attr_type => :boolean, :width => 40, :header => "Incl", :default_value => true},
67
- {:name => "name", :attr_type => :string, :editor => :combobox, :width => 200},
67
+ {:name => "name", :attr_type => :string, :editor => :netzkeremotecombo, :width => 200},
68
68
  {:name => "label", :attr_type => :string, :header => "Label"},
69
69
  {:name => "default_value", :attr_type => :string}
70
70
  ]
@@ -98,13 +98,13 @@ module Netzke
98
98
  detect_association_with_method(field) # xtype for an association field
99
99
  set_default_field_label(field)
100
100
  set_default_field_xtype(field) if field[:xtype].nil?
101
- set_default_field_value(field) if self.record
101
+ # set_default_field_value(field) if self.record
102
102
  set_default_read_only(field)
103
103
 
104
104
  field[:display_mode] = config[:display_mode] if config[:display_mode]
105
105
 
106
106
  # provide our special combobox with our id
107
- field[:parent_id] = self.global_id if field[:xtype] == :combobox
107
+ field[:parent_id] = self.global_id if field[:xtype] == :netzkeremotecombo
108
108
 
109
109
  field[:hidden] = field[:hide_label] = true if field[:hidden].nil? && primary_key_attr?(field)
110
110
 
@@ -122,12 +122,10 @@ module Netzke
122
122
  if method && assoc = data_class.reflect_on_association(assoc_name)
123
123
  assoc_column = assoc.klass.columns_hash[method.to_s]
124
124
  assoc_method_type = assoc_column.try(:type)
125
- if assoc_method_type
126
- if c[:nested_attribute]
127
- c[:xtype] ||= xtype_for_attr_type(assoc_method_type)
128
- else
129
- c[:xtype] ||= assoc_method_type == :boolean ? xtype_for_attr_type(assoc_method_type) : xtype_for_association
130
- end
125
+ if c[:nested_attribute]
126
+ c[:xtype] ||= xtype_for_attr_type(assoc_method_type)
127
+ else
128
+ c[:xtype] ||= assoc_method_type == :boolean ? xtype_for_attr_type(assoc_method_type) : xtype_for_association
131
129
  end
132
130
  end
133
131
  end
@@ -161,10 +159,10 @@ module Netzke
161
159
  c[:field_label].gsub!(/\s+/, " ")
162
160
  end
163
161
 
164
- def set_default_field_value(field)
165
- value = record.value_for_attribute(field)
166
- field[:value] ||= value unless value.nil?
167
- end
162
+ # def set_default_field_value(field)
163
+ # value = record.value_for_attribute(field)
164
+ # field[:value] ||= value unless value.nil?
165
+ # end
168
166
 
169
167
  # Deeply merges only those key/values at the top level that are already there
170
168
  def deep_merge_existing_fields(dest, src)
@@ -191,7 +189,7 @@ module Netzke
191
189
  :integer => :numberfield,
192
190
  :boolean => :checkbox,
193
191
  :date => :datefield,
194
- :datetime => :xdatetime,
192
+ :datetime => :datetimefield,
195
193
  :text => :textarea,
196
194
  :json => :jsonfield,
197
195
  :string => :textfield
@@ -203,7 +201,7 @@ module Netzke
203
201
  end
204
202
 
205
203
  def xtype_for_association
206
- :combobox
204
+ :netzkeremotecombo
207
205
  end
208
206
 
209
207
  # Are we provided with a static field layout?
@@ -53,7 +53,14 @@ Ext.netzke.form.CommaListCbg = Ext.extend(Ext.form.CheckboxGroup, {
53
53
  } else {
54
54
  Ext.netzke.form.CommaListCbg.superclass.setValue.call(this, arguments);
55
55
  }
56
+ },
57
+
58
+ setReadonlyMode: function(onOff) {
59
+ this.items.each(function(i){
60
+ i.setReadonlyMode(onOff);
61
+ });
56
62
  }
63
+
57
64
  });
58
65
 
59
66
  Ext.reg('commalistcbg', Ext.netzke.form.CommaListCbg);
@@ -27,6 +27,12 @@ Ext.override(Ext.form.Field, {
27
27
 
28
28
  });
29
29
 
30
+ Ext.override(Ext.netzke.ComboBox, {
31
+ displayModeConfig: function(onOff){
32
+ return Ext.apply(this.initialConfig, onOff ? {xtype: 'displayfield', origXtype: this.xtype, value: this.getRawValue(), origValue: this.getValue()} : {xtype: this.origXtype, value: this.origValue});
33
+ }
34
+ });
35
+
30
36
  Ext.override(Ext.netzke.form.NRadioGroup, {
31
37
  setDisplayMode: function(onOff){
32
38
  this.items.each(function(i){
@@ -22,29 +22,32 @@
22
22
  // Now let Ext.form.FormPanel do the rest
23
23
  Netzke.classes.Basepack.FormPanel.superclass.initComponent.call(this);
24
24
 
25
- // Apply event
25
+ // To inform the parent about the apply event
26
26
  this.addEvents('apply');
27
27
  },
28
28
 
29
29
  afterRender: function(){
30
30
  Netzke.classes.Basepack.FormPanel.superclass.afterRender.call(this);
31
31
 
32
+ // have a record to be displayed?
33
+ if (this.record) { this.setFormValues(this.record); }
34
+
32
35
  // render in display mode?
33
- if (this.locked) this.setDisplayMode(true);
36
+ if (this.locked) this.setReadonlyMode(true);
34
37
  },
35
38
 
36
39
  onEdit: function(){
37
- this.setDisplayMode(false);
40
+ this.setReadonlyMode(false);
38
41
  },
39
42
 
40
43
  onCancel: function(){
41
- this.setDisplayMode(true, true);
44
+ this.setReadonlyMode(true, true);
42
45
  },
43
46
 
44
47
  updateToolbar: function(){
45
48
  var tbar = this.getBottomToolbar();
46
49
 
47
- if (this.inDisplayMode) {
50
+ if (this.inReadonlyMode) {
48
51
  var buttonIndex = tbar.items.findIndex("name", "apply");
49
52
  var buttonToRemove = tbar.items.itemAt(buttonIndex);
50
53
  if (buttonToRemove) {
@@ -73,7 +76,7 @@
73
76
 
74
77
  onApply: function() {
75
78
  if (this.fireEvent('apply', this)) {
76
- var values = this.getForm().getValues();
79
+ var values = this.getForm().getFieldValues();
77
80
 
78
81
  // do not send values from disabled fields and empty values
79
82
  for (var fieldName in values) {
@@ -88,7 +91,7 @@
88
91
  if (this.fileUpload) {
89
92
  // Not a Netzke's standard endpoint call, because the form is multipart
90
93
  this.getForm().submit({
91
- url: this.endpointUrl("netzke_submit"),
94
+ api: {submit: Netzke.providers[this.parentId].netzkeSubmit},
92
95
  params: {
93
96
  data: Ext.encode(values)
94
97
  },
@@ -105,7 +108,7 @@
105
108
  this.netzkeSubmit(Ext.apply((this.baseParams || {}), {data:Ext.encode(values)}), function(result){
106
109
  if (result === "ok") {
107
110
  this.fireEvent("submitsuccess");
108
- if (this.mode == "lockable") this.setDisplayMode(true);
111
+ if (this.mode == "lockable") this.setReadonlyMode(true);
109
112
  };
110
113
  if (this.applyMaskCmp) this.applyMaskCmp.hide();
111
114
  }, this);
@@ -114,6 +117,19 @@
114
117
  },
115
118
 
116
119
  setFormValues: function(values){
120
+ var assocValues = values._meta.associationValues || {};
121
+ for (var assocFieldName in assocValues) {
122
+ var assocField = this.find('name', assocFieldName)[0];
123
+ if (this.inDisplayMode) {
124
+ assocField.origValue = values[assocFieldName];
125
+ assocField.setValue(assocValues[assocFieldName]);
126
+ delete values[assocFieldName]; // we don't want this to be set once more below with setValues()
127
+ } else {
128
+ assocField.getStore().loadData({data: [[values[assocFieldName], assocValues[assocFieldName]]]});
129
+ delete assocField.lastQuery; // force loading the store next time user clicks the trigger
130
+ }
131
+ }
132
+
117
133
  this.getForm().setValues(values);
118
134
  },
119
135
 
@@ -126,13 +142,31 @@
126
142
  this.updateToolbar();
127
143
  },
128
144
 
145
+ setReadonlyMode: function(onOff, cancel){
146
+ if (this.inReadonlyMode == onOff) return;
147
+ this.getForm().items.each(function(i){i.setReadonlyMode(onOff);});
148
+ // this.getForm().cleanDestroyed(); // because fields inside of composite fields are not auto-cleaned!
149
+ this.doLayout();
150
+ this.inReadonlyMode = onOff;
151
+ this.updateToolbar();
152
+ },
153
+
129
154
  // recursively extract field names
130
155
  extractFields: function(items){
131
156
  Ext.each(items, function(i){
132
157
  if (i.items) {this.extractFields(i.items);}
133
158
  else if (i.name) {this.fieldNames.push(i.name);}
134
159
  }, this);
160
+ },
135
161
 
162
+ applyFormErrors: function(errors) {
163
+ var field;
164
+ Ext.iterate(errors, function(fieldName, message){
165
+ fieldName = fieldName.underscore();
166
+ if ( field = this.getForm().findField(fieldName) || this.getForm().findField(fieldName.replace(/([a-z]+)([0-9])/g, '$1_$2'))) {
167
+ field.markInvalid(message.join('<br/>'));
168
+ }
169
+ }, this);
136
170
  }
137
171
 
138
172
  }
@@ -1,24 +1,41 @@
1
1
  Ext.ns("Ext.netzke.form");
2
-
3
2
  /*
4
3
  A very simple RadioGroup extension.
5
4
  Config options:
6
- * options - all radio buttons, by boxLabel, e.g.: ["Cool", "To read", "Important"]
5
+ * options:
6
+ 1) all radio buttons, by boxLabel, e.g.: ["Cool", "To read", "Important"]
7
+ 2) array of arrays in format [value, label], e.g.: [[1, "Good"], [2, "Average"], [3, "Poor"]]
7
8
  */
8
9
  Ext.netzke.form.NRadioGroup = Ext.extend(Ext.form.RadioGroup, {
9
- // defaultType: 'radio',
10
- // groupCls : 'x-form-radio-group',
11
-
12
10
  initComponent: function(){
13
11
  Ext.netzke.form.NRadioGroup.superclass.initComponent.call(this);
14
12
 
15
13
  this.items = [];
16
14
 
17
15
  Ext.each(this.options, function(o){
18
- this.items.push({boxLabel: o, name: this.name, inputValue: o});
16
+ if (Ext.isArray(o)){
17
+ this.items.push({boxLabel: o[1], name: this.name, inputValue: o[0]});
18
+ } else {
19
+ this.items.push({boxLabel: o, name: this.name, inputValue: o});
20
+ }
19
21
  }, this);
20
22
 
21
- delete this.name;
23
+ // delete this.name;
24
+ },
25
+
26
+ getValue: function() {
27
+ var value;
28
+ this.items.each(function(i) {
29
+ value = i.inputValue;
30
+ return !i.getValue();
31
+ });
32
+ return value;
33
+ },
34
+
35
+ setReadonlyMode: function(onOff) {
36
+ this.items.each(function(i){
37
+ i.setReadonlyMode(onOff);
38
+ });
22
39
  }
23
40
  });
24
41
 
@@ -0,0 +1,52 @@
1
+ Ext.override(Ext.form.Field, {
2
+
3
+ // By calling this, the field is instructed to replace itself with another instance, configured with displayModeConfig
4
+ setReadonlyMode: function(onOff){
5
+ if (this.hidden) return;
6
+
7
+ this.setReadOnly(onOff);
8
+
9
+ if (onOff) {
10
+ this.addClass("readonly");
11
+ if (this.label) this.label.addClass("readonly");
12
+ } else {
13
+ this.removeClass("readonly");
14
+ if (this.label) this.label.removeClass("readonly");
15
+ }
16
+ // var owner = this.ownerCt;
17
+ // var newConfig = this.readonlyModeConfig(onOff);
18
+ //
19
+ // var idx = this.removeSelf();
20
+ // owner.insert(idx, newConfig);
21
+ //
22
+ // this.destroy();
23
+ },
24
+
25
+ // Remove self from the container
26
+ // removeSelf: function(){
27
+ // var idx = this.ownerCt.items.indexOf(this);
28
+ // this.ownerCt.remove(this);
29
+ // return idx;
30
+ // },
31
+
32
+ // Config for creating an instance in "displayMode" (if onOff is true), or normal mode (if onOff is false)
33
+ // displayModeConfig: function(onOff){
34
+ // return Ext.apply(this.initialConfig, onOff ? {xtype: 'displayfield', origXtype: this.xtype, value: this.getValue()} : {xtype: this.origXtype, value: this.getValue()});
35
+ // }
36
+
37
+ });
38
+
39
+ // Composite field has to take care of its children, by setting them into the "display mode"
40
+ Ext.override(Ext.form.CompositeField, {
41
+ setReadonlyMode: function(onOff){
42
+ this.items.each(function(i){
43
+ i.setReadonlyMode(onOff);
44
+ });
45
+ }
46
+ });
47
+
48
+ Ext.override(Ext.form.Checkbox, {
49
+ setReadonlyMode: function(onOff){
50
+ this.setDisabled(onOff);
51
+ }
52
+ });
@@ -51,18 +51,44 @@ module Netzke
51
51
  success = create_or_update_record(data)
52
52
 
53
53
  if success
54
- {:set_form_values => values.each_pair.inject({}){ |r,(k,v)| r.merge(k.l => v) }, :set_result => "ok"}
54
+ {:set_form_values => js_record_data, :set_result => "ok"}
55
55
  else
56
56
  # flash eventual errors
57
57
  @record.errors.to_a.each do |msg|
58
58
  flash :error => msg
59
59
  end
60
- {:feedback => @flash}
60
+ # {:feedback => @flash}
61
+ {:feedback => @flash, :apply_form_errors => build_form_errors(record)}
61
62
  end
62
63
  end
63
64
 
64
65
  private
65
66
 
67
+ # Builds the form errors
68
+ def build_form_errors(record)
69
+ form_errors = {}
70
+ foreign_keys = {}
71
+
72
+ # Build a hash of foreign keys and the associated model
73
+ data_class.reflect_on_all_associations(:belongs_to).map{ |r|
74
+ foreign_keys[r.association_foreign_key.to_sym] = r.name
75
+ }
76
+
77
+ record.errors.map{|field, error|
78
+ # Get the correct field name for the errors on foreign keys
79
+ if foreign_keys.has_key?(field)
80
+ fields.each do |k, v|
81
+ # Hack to stop to_nifty_json from camalizing model__field
82
+ field = k.to_s.gsub('__', '____') if k.to_s.split('__').first == foreign_keys[field].to_s
83
+ end
84
+ end
85
+
86
+ form_errors[field] ||= []
87
+ form_errors[field] << error
88
+ }
89
+ form_errors
90
+ end
91
+
66
92
  # Creates/updates a record from hash
67
93
  def create_or_update_record(hsh)
68
94
 
@@ -0,0 +1,14 @@
1
+ input.readonly, textarea.readonly {
2
+ background-color: #f5f5f5;
3
+ background-image: none;
4
+ }
5
+
6
+ /*input.readonly, textarea.readonly {
7
+ border: 0 solid #fff;
8
+ background-image: none;
9
+ }
10
+
11
+ label.readonly {
12
+ font-weight: bold;
13
+ }
14
+ */
@@ -1,49 +1,29 @@
1
1
  require "netzke/basepack/grid_panel/columns"
2
2
  require "netzke/basepack/grid_panel/services"
3
3
  # require "netzke/basepack/plugins/configuration_tool"
4
- # require "data_accessor"
5
4
 
6
5
  module Netzke
7
6
  module Basepack
8
- # = GridPanel
9
- #
10
7
  # Ext.grid.EditorGridPanel-based component with the following features:
11
8
  #
9
+ # * ActiveRecord-model support with automatic column configuration
12
10
  # * multi-line CRUD operations - get, post, delete, create
13
11
  # * (multe-record) editing and adding records through a form
14
- # * column resize, move and hide
12
+ # * persistent column resize, move and hide
15
13
  # * permissions
16
14
  # * sorting
17
15
  # * pagination
18
16
  # * filtering
19
- # * extended search
20
- # * (TODO) rows reordering (drag-n-drop)
17
+ # * advanced search
18
+ # * rows reordering by drag-n-drop, requires acts_as_list on the model
19
+ # * virtual attribute support
21
20
  # * (TODO) dynamic configuration of properties and columns
22
21
  #
23
- # == Class configuration
24
- #
25
- # Configuration on this level is effective during the life-time of the application. The right place for setting these options are in
26
- # config/initializers, e.g.:
27
- #
28
- # Netzke::GridPanel.column_filters_available = false
29
- # Netzke::GridPanel.default_config = {:enable_config_tool => false}
30
- #
31
- # Most of these options influence the amount of JavaScript code that is generated for this component's class, in the way that
32
- # the less functionality is enabled, the less code is generated.
33
- #
34
- # The following configuration options are available:
35
- # * <tt>:column_filters_available</tt> - (default is true) include code for the filters in the column's context menu
36
- # * (TODO)<tt>:config_tool_available</tt> - (default is true) include code for the configuration tool that launches the configuration panel
37
- # * <tt>:edit_in_form_available</tt> - (defaults to true) include code for (multi-record) editing and adding records through a form
38
- # * <tt>:extended_search_available</tt> - (defaults to true) include code for extended configurable search
39
- # * <tt>:default_config</tt> - a hash of default configuration options for each instance of the GridPanel component.
40
- # See the "Instance configuration" section below.
41
- #
42
22
  # == Instance configuration
43
- # The following config options are available:
44
- # * <tt>:model</tt> - name of the ActiveRecord model that provides data to this GridPanel.
45
- # * <tt>:strong_default_attrs</tt> - a hash of attributes to be merged atop of every created/updated record.
46
- # * <tt>:scope</tt> - specifies how the data should be filtered.
23
+ # The following config options are supported:
24
+ # * +model+ - name of the ActiveRecord model that provides data to this GridPanel, e.g. "User"
25
+ # * +columns+ - an array of columns to be displayed in the grid; each column may be represented by a symbol (representing the model's attribute name), or a hash (when extra configuration is needed). See the "Columns" section below.
26
+ # * +scope+ - specifies how the data should be filtered.
47
27
  # When it's a symbol, it's used as a scope name.
48
28
  # When it's a string, it's a SQL statement (passed directly to +where+).
49
29
  # When it's a hash, it's a conditions hash (passed directly to +where+).
@@ -55,28 +35,33 @@ module Netzke
55
35
  #
56
36
  # :scope => { |rel| rel.where(:id.gt => 100).order(:created_at) }
57
37
  #
58
- # * <tt>:enable_column_filters</tt> - enable filters in column's context menu
59
- # * <tt>:enable_edit_in_form</tt> - provide buttons into the toolbar that activate editing/adding records via a form
60
- # * <tt>:enable_extended_search</tt> - provide a button into the toolbar that shows configurable search form
61
- # * <tt>:enable_context_menu</tt> - enable rows context menu
62
- # * <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>
63
- # * <tt>:enable_pagination</tt> - enable pagination; defaults to <tt>true</tt>
64
- # * <tt>:rows_per_page</tt> - number of rows per page (ignored when <tt>:enable_pagination</tt> is set to <tt>false</tt>)
65
- # * <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>
66
- # * (TODO) <tt>:mode</tt> - when set to <tt>:config</tt>, GridPanel loads in configuration mode
67
- # * <tt>:add/edit/multi_edit/search_form_config</tt> - additional configuration for add/edit/multi_edit/search form panel
68
- # * <tt>:add/edit/multi_edit_form_window_config</tt> - additional configuration for the window that wrapps up add/edit/multi_edit form panel
69
- # * <tt>:columns</tt> - an array of columns to be displayed in the grid; each column may be represented by a symbol (representing the model's attribute name), or a hash (when extra configuration is needed)
38
+ # * +strong_default_attrs+ - (defaults to {}) a hash of attributes to be merged atop of every created/updated record, e.g. {:role_id => 1}
39
+ # * +enable_column_filters+ - (defaults to true) enable filters in column's context menu
40
+ # * +enable_edit_in_form+ - (defaults to true) provide buttons into the toolbar that activate editing/adding records via a form
41
+ # * +enable_extended_search+ - (defaults to true) provide a button into the toolbar that shows configurable search form
42
+ # * +enable_context_menu+ - (defaults to true) enable rows context menu
43
+ # * +enable_rows_reordering+ - (defaults to false) enable reordering of rows with drag-n-drop; underlying model (specified in +model+) must implement "acts_as_list"-compatible functionality
44
+ # * +enable_pagination+ - (defaults to true) enable pagination
45
+ # * +rows_per_page+ - (defaults to 30) number of rows per page (ignored when +enable_pagination+ is set to <tt>false+)
46
+ # * +load_inline_data+ - (defaults to true) load initial data into the grid right after its instantiation
47
+ # * (TODO) +mode+ - when set to +config+, GridPanel loads in configuration mode
48
+ # * +add/edit/multi_edit/search_form_config+ - additional configuration for add/edit/multi_edit/search form panel
49
+ # * +add/edit/multi_edit_form_window_config+ - additional configuration for the window that wrapps up add/edit/multi_edit form panel
70
50
  #
71
51
  # == Columns
72
- # Columns are configured by passing an array to the +columns+ option. Each element in the array is either the name of model's (virtual) attribute, or a column configuration hash.
73
- # The column configuration hash recognizes the following options:
52
+ # Columns are configured by passing an array to the +columns+ option. Each element in the array is either the name of model's (virtual) attribute (in which case the configuration will be fully automatic), or a hash that may contain the following configuration options as keys:
74
53
  #
75
- # * +name+ - name of the column, that may correspond to model's (virtual) attribute
54
+ # * +name+ - name of the column, that may correspond to the model's (virtual) attribute
76
55
  # * +read_only+ - a boolean that defines if the cells in the column should be editable
77
56
  # * +editable+ - same as +read_only+, but in reverse (takes precedence over +read_only+)
78
- # * +getter+ - a lambda that receives a record as a parameter, and is expected to return a string that will be printed in the cell (can be HTML code)
79
- # * +setter+ - a lambda that receives a record as first parameter, and the value passed from the cell as the second parameter, and is expected to modify the record accordingly
57
+ # * +filterable+ - set to false to disable filtering on this column
58
+ # * +getter+ - a lambda that receives a record as a parameter, and is expected to return a string that will be sent to the cell (can be HTML code), e.g.:
59
+ #
60
+ # :getter => lambda {|r| [r.first_name, r.last_name].join }
61
+
62
+ # * +setter+ - a lambda that receives a record as first parameter, and the value passed from the cell as the second parameter, and is expected to modify the record accordingly, e.g.:
63
+ #
64
+ # :setter => lambda { |r,v| r.first_name, r.last_name = v.split(" ") }
80
65
  #
81
66
  # * +sorting_scope+ - the name of the scope used for sorting the column. This can be useful for virtual columns for example. The scope will get one parameter specifying the direction (:asc or :desc). Example:
82
67
  #
@@ -87,138 +72,127 @@ module Netzke
87
72
  # order("users.first_name #{dir.to_s}, users.last_name #{dir.to_s}")
88
73
  # }
89
74
  # end
75
+ #
90
76
  # Besides these options, a column can receive any meaningful config option understood by Ext.grid.Column (http://dev.sencha.com/deploy/dev/docs/?class=Ext.grid.Column)
91
77
  #
92
78
  # == Actions
93
- # You can override GridPanel's actions to change their text, icons, and tooltips (see http://api.netzke.org/core/Netzke/Actions.html). You can also use these actions when configuring menus and toolbars.
79
+ # You can override GridPanel's actions to change their text, icons, and tooltips (see http://api.netzke.org/core/Netzke/Actions.html).
80
+ #
94
81
  # GridPanel implements the following actions:
95
- # * +add+
96
- # * +del+
97
- # * +edit+
98
- # * +apply+
99
- # * +add_in_form+
100
- # * +edit_in_form+
101
- # * +search+
82
+ # * +add+ - inline adding of a record
83
+ # * +del+ - deletion of records
84
+ # * +edit+ - inline editing of a record
85
+ # * +apply+ - applying inline changes
86
+ # * +add_in_form+ - adding a record in a form
87
+ # * +edit_in_form+ - (multi-record) editing in a forrm
88
+ # * +search+ - advanced searching
89
+ #
90
+ # == Class configuration
91
+ #
92
+ # Configuration on this level is effective during the life-time of the application. One place for setting these options are in application.rb, e.g.:
93
+ #
94
+ # config.netzke.basepack.grid_panel.column_filters_available = false
95
+ #
96
+ # These can also be eventually set directly on the component's class:
97
+ #
98
+ # Netzke::Basepack::GridPanel.column_filters_available = false
99
+ #
100
+ # Most of these options influence the amount of JavaScript code that is generated for this component's class, in the way that the less functionality is enabled, the less code is generated.
101
+ #
102
+ # The following class configuration options are available:
103
+ # * +column_filters_available+ - (defaults to true) include code for the filters in the column's context menu
104
+ # * (TODO)+config_tool_available+ - (defaults to true) include code for the configuration tool that launches the configuration panel
105
+ # * +edit_in_form_available+ - (defaults to true) include code for (multi-record) editing and adding records through a form
106
+ # * +extended_search_available+ - (defaults to true) include code for extended configurable search
102
107
  class GridPanel < Netzke::Base
108
+ js_base_class "Ext.grid.EditorGridPanel"
109
+
103
110
  # Class-level configuration. These options directly influence the amount of generated
104
111
  # javascript code for this component's class. For example, if you don't want filters for the grid,
105
112
  # set column_filters_available to false, and the javascript for the filters won't be included at all.
106
- class_attribute :column_filters_available
107
- self.column_filters_available = true
113
+ class_config_option :column_filters_available, true
108
114
 
109
- class_attribute :config_tool_available
110
- self.config_tool_available = true
115
+ class_config_option :extended_search_available, true
111
116
 
112
- class_attribute :edit_in_form_available
113
- self.edit_in_form_available = true
117
+ class_config_option :edit_in_form_available, true
114
118
 
115
- class_attribute :extended_search_available
116
- self.extended_search_available = true
119
+ class_config_option :rows_reordering_available, true
117
120
 
118
- class_attribute :rows_reordering_available
119
- self.rows_reordering_available = true
121
+ class_config_option :config_tool_available, true
120
122
 
121
- class_attribute :default_config
122
- self.default_config = {
123
- :enable_edit_in_form => true,
124
- :enable_extended_search => true,
125
- :enable_column_filters => true,
123
+ class_config_option :default_instance_config, {
124
+ :enable_edit_in_form => edit_in_form_available,
125
+ :enable_extended_search => extended_search_available,
126
+ :enable_column_filters => column_filters_available,
126
127
  :load_inline_data => true,
127
128
  :enable_rows_reordering => false, # column drag n drop
128
129
  :enable_pagination => true,
129
- :rows_per_page => 25,
130
+ :rows_per_page => 30,
130
131
  :tools => %w{ refresh },
131
132
  }
132
133
 
134
+ extend ActiveSupport::Memoizable
135
+
133
136
  include self::Services
134
137
  include self::Columns
138
+ include Netzke::Basepack::DataAccessor
135
139
 
136
- include Netzke::DataAccessor
137
-
138
- # def self.enforce_config_consistency
139
- # default_config[:enable_edit_in_form] &&= edit_in_form_available
140
- # default_config[:enable_extended_search] &&= extended_search_available
141
- # default_config[:enable_rows_reordering] &&= rows_reordering_available
142
- # end
143
-
144
- # def initialize(*args)
145
- # # Deprecations
146
- # config[:scopes] && ActiveSupport::Deprecation.warn(":scopes option is not effective any longer for GridPanel. Use :scope instead.")
147
- #
148
- # super(*args)
149
- # end
150
-
151
- js_base_class "Ext.grid.EditorGridPanel"
152
- js_mixin :main
140
+ js_mixin :grid_panel
153
141
  js_mixin :advanced_search if extended_search_available
154
142
  js_mixin :edit_in_form if edit_in_form_available
155
143
 
156
- # I18n used in JavaScript
157
- js_property :i18n, {
158
- :are_you_sure => I18n.translate("netzke.basepack.generic.are_you_sure"),
159
- :confirm => I18n.translate("netzke.basepack.generic.confirm")
160
- }
161
-
162
- # Include extra javascript that we depend on
163
- def self.include_js
164
- res = []
165
- ext_examples = Netzke::Core.ext_location.join("examples")
166
-
167
- # Checkcolumn
168
- res << ext_examples.join("ux/CheckColumn.js")
144
+ js_translate *%w[are_you_sure confirmation first_text prev_text next_text last_text before_page_text after_page_text empty_msg refresh_text display_msg]
169
145
 
170
- # Filters
171
- if column_filters_available
172
- res << ext_examples + "ux/gridfilters/menu/ListMenu.js"
173
- res << ext_examples + "ux/gridfilters/menu/RangeMenu.js"
174
- res << ext_examples + "ux/gridfilters/GridFilters.js"
146
+ # JavaScript includes
147
+ ex = Netzke::Core.ext_location.join("examples")
175
148
 
176
- %w{Boolean Date List Numeric String}.unshift("").each do |f|
177
- res << ext_examples + "ux/gridfilters/filter/#{f}Filter.js"
178
- end
149
+ js_include(ex.join("ux/CheckColumn.js"))
179
150
 
180
- # Fix
181
- res << "#{File.dirname(__FILE__)}/grid_panel/javascripts/misc.js"
182
- end
151
+ # Includes for column filters
152
+ if column_filters_available
153
+ [
154
+ "ux/gridfilters/menu/ListMenu.js",
155
+ "ux/gridfilters/menu/RangeMenu.js",
156
+ "ux/gridfilters/GridFilters.js"
157
+ ].each{ |path| js_include(ex.join(path)) }
183
158
 
184
- # DD
185
- if rows_reordering_available
186
- res << "#{File.dirname(__FILE__)}/grid_panel/javascripts/rows-dd.js"
159
+ %w{Boolean Date List Numeric String}.unshift("").each do |f|
160
+ js_include(ex.join"ux/gridfilters/filter/#{f}Filter.js")
187
161
  end
188
-
189
- res
190
162
  end
191
163
 
192
- # Fields to be displayed in the "General" tab of the configuration panel
193
- def self.property_fields
194
- [
195
- # {:name => :ext_config__title, :attr_type => :string},
196
- # {:name => :ext_config__header, :attr_type => :boolean, :default => true},
197
- # {:name => :ext_config__enable_context_menu, :attr_type => :boolean, :default => true},
198
- # {:name => :ext_config__enable_pagination, :attr_type => :boolean, :default => true},
199
- # {:name => :ext_config__rows_per_page, :attr_type => :integer},
200
- # {:name => :ext_config__prohibit_create, :attr_type => :boolean},
201
- # {:name => :ext_config__prohibit_update, :attr_type => :boolean},
202
- # {:name => :ext_config__prohibit_delete, :attr_type => :boolean},
203
- # {:name => :ext_config__prohibit_read, :attr_type => :boolean}
204
- ]
164
+ # Includes for rows reordering
165
+ if rows_reordering_available
166
+ js_include(ex.join("#{File.dirname(__FILE__)}/grid_panel/javascripts/rows-dd.js"))
205
167
  end
206
168
 
207
-
208
- # The result of this method (a hash) is converted to a JSON object and passed as the configuration parameter
209
- # to the constructor of our JavaScript class. Override it when you want to pass any extra configuration
210
- # to the JavaScript side.
211
169
  def js_config
212
170
  super.merge({
213
171
  :bbar => config.has_key?(:bbar) ? config[:bbar] : default_bbar,
214
172
  :context_menu => config.has_key?(:context_menu) ? config[:context_menu] : default_context_menu,
215
- :columns => columns, # columns
173
+ :columns => columns(:with_meta => true), # columns
174
+ :columns_order => config[:persistence] && state[:columns_order] || initial_columns_order,
216
175
  :model => config[:model], # the model name
217
176
  :inline_data => (get_data if config[:load_inline_data]), # inline data (loaded along with the grid panel)
218
177
  :pri => data_class.primary_key # table primary key name
219
178
  })
220
179
  end
221
180
 
181
+ def get_association_values(record)
182
+ columns.select{ |c| c[:name].index("__") }.each.inject({}) do |r,c|
183
+ r.merge(c[:name] => record.value_for_attribute(c, true))
184
+ end
185
+ end
186
+
187
+ def get_default_association_values
188
+ columns.select{ |c| c[:name].index("__") && c[:default_value] }.each.inject({}) do |r,c|
189
+ assoc, assoc_method = assoc_and_assoc_method_for_column(c)
190
+ assoc_instance = assoc.klass.find(c[:default_value])
191
+ r.merge(c[:name] => assoc_instance.send(assoc_method))
192
+ end
193
+ end
194
+ memoize :get_default_association_values
195
+
222
196
  def default_bbar
223
197
  res = %w{ add edit apply del }.map(&:to_sym).map(&:action)
224
198
  res << "-" << :add_in_form.action << :edit_in_form.action if config[:enable_edit_in_form]
@@ -259,19 +233,23 @@ module Netzke
259
233
  }
260
234
  end
261
235
 
262
- action :edit, {
263
- :text => I18n.t('netzke.basepack.grid_panel.actions.edit'),
264
- :tooltip => I18n.t('netzke.basepack.grid_panel.actions.edit'),
265
- :disabled => true,
266
- :icon => :table_edit
267
- }
236
+ action :edit do
237
+ {
238
+ :text => I18n.t('netzke.basepack.grid_panel.actions.edit'),
239
+ :tooltip => I18n.t('netzke.basepack.grid_panel.actions.edit'),
240
+ :disabled => true,
241
+ :icon => :table_edit
242
+ }
243
+ end
268
244
 
269
- action :del, {
270
- :text => I18n.t('netzke.basepack.grid_panel.actions.del'),
271
- :tooltip => I18n.t('netzke.basepack.grid_panel.actions.del'),
272
- :disabled => true,
273
- :icon => :table_row_delete
274
- }
245
+ action :del do
246
+ {
247
+ :text => I18n.t('netzke.basepack.grid_panel.actions.del'),
248
+ :tooltip => I18n.t('netzke.basepack.grid_panel.actions.del'),
249
+ :disabled => true,
250
+ :icon => :table_row_delete
251
+ }
252
+ end
275
253
 
276
254
  action :apply do
277
255
  {
@@ -282,25 +260,31 @@ module Netzke
282
260
  }
283
261
  end
284
262
 
285
- action :add_in_form, {
286
- :text => I18n.t('netzke.basepack.grid_panel.actions.add_in_form'),
287
- :tooltip => I18n.t('netzke.basepack.grid_panel.actions.add_in_form'),
288
- :icon => :application_form_add
289
- }
263
+ action :add_in_form do
264
+ {
265
+ :text => I18n.t('netzke.basepack.grid_panel.actions.add_in_form'),
266
+ :tooltip => I18n.t('netzke.basepack.grid_panel.actions.add_in_form'),
267
+ :icon => :application_form_add
268
+ }
269
+ end
290
270
 
291
- action :edit_in_form, {
292
- :text => I18n.t('netzke.basepack.grid_panel.actions.edit_in_form'),
293
- :tooltip => I18n.t('netzke.basepack.grid_panel.actions.edit_in_form'),
294
- :disabled => true,
295
- :icon => :application_form_edit
296
- }
271
+ action :edit_in_form do
272
+ {
273
+ :text => I18n.t('netzke.basepack.grid_panel.actions.edit_in_form'),
274
+ :tooltip => I18n.t('netzke.basepack.grid_panel.actions.edit_in_form'),
275
+ :disabled => true,
276
+ :icon => :application_form_edit
277
+ }
278
+ end
297
279
 
298
- action :search, {
299
- :text => I18n.t('netzke.basepack.grid_panel.actions.search'),
300
- :tooltip => I18n.t('netzke.basepack.grid_panel.actions.search'),
301
- :enable_toggle => true,
302
- :icon => :find
303
- }
280
+ action :search do
281
+ {
282
+ :text => I18n.t('netzke.basepack.grid_panel.actions.search'),
283
+ :tooltip => I18n.t('netzke.basepack.grid_panel.actions.search'),
284
+ :enable_toggle => true,
285
+ :icon => :find
286
+ }
287
+ end
304
288
 
305
289
  component :add_form do
306
290
  {
@@ -311,14 +295,14 @@ module Netzke
311
295
  :items => [{
312
296
  :class_name => "Netzke::Basepack::FormPanel",
313
297
  :model => config[:model],
314
- :items => default_fields_for_forms_with_default_values,
298
+ :items => default_fields_for_forms,
315
299
  :persistent_config => config[:persistent_config],
316
300
  :strong_default_attrs => config[:strong_default_attrs],
317
301
  :border => true,
318
302
  :bbar => false,
319
303
  :header => false,
320
304
  :mode => config[:mode],
321
- :record => data_class.new
305
+ :record => data_class.new(columns_default_values)
322
306
  }.deep_merge(config[:add_form_config] || {})]
323
307
  }.deep_merge(config[:add_form_window_config] || {})
324
308
  end
@@ -337,7 +321,7 @@ module Netzke
337
321
  :bbar => false,
338
322
  :header => false,
339
323
  :mode => config[:mode]
340
- # :record_id gets assigned by deliver_component at the moment of loading
324
+ # :record_id gets assigned by deliver_component dynamically, at the moment of loading
341
325
  }.deep_merge(config[:edit_form_config] || {})]
342
326
  }.deep_merge(config[:edit_form_window_config] || {})
343
327
  end
@@ -363,26 +347,12 @@ module Netzke
363
347
  component :search_form do
364
348
  {
365
349
  :lazy_loading => true,
366
- :class_name => "Netzke::Basepack::GridPanel::SearchWindow",
350
+ :class_name => "Netzke::Basepack::SearchWindow",
367
351
  :model => config[:model],
368
352
  :fields => default_fields_for_forms
369
353
  }
370
354
  end
371
355
 
372
-
373
- # def search_panel
374
- # {
375
- # :class_name => "Netzke::Basepack::FormPanel",
376
- # :model => "User",
377
- # # :items => default_fields_for_forms,
378
- # # :search_class_name => cronfig[:model],
379
- # # :persistent_config => config[:persistent_config],
380
- # :header => false,
381
- # :bbar => false,
382
- # # :mode => config[:mode]
383
- # }
384
- # end
385
-
386
356
  # include ::Netzke::Plugins::ConfigurationTool if config_tool_available # it will load ConfigurationPanel into a modal window
387
357
 
388
358
  end