netzke-basepack 0.5.8 → 0.5.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. data/CHANGELOG.rdoc +18 -0
  2. data/README.rdoc +11 -4
  3. data/Rakefile +19 -3
  4. data/TODO.rdoc +1 -1
  5. data/generators/netzke_basepack/netzke_basepack_generator.rb +13 -0
  6. data/generators/netzke_basepack/templates/create_netzke_field_lists.rb +18 -0
  7. data/generators/netzke_basepack/templates/public_assets/ts-checkbox.gif +0 -0
  8. data/install.rb +1 -1
  9. data/javascripts/basepack.js +124 -30
  10. data/lib/app/models/netzke_field_list.rb +261 -0
  11. data/lib/app/models/netzke_model_attr_list.rb +21 -0
  12. data/lib/app/models/netzke_persistent_array_auto_model.rb +58 -0
  13. data/lib/netzke-basepack.rb +17 -2
  14. data/lib/netzke/active_record.rb +10 -0
  15. data/lib/netzke/active_record/association_attributes.rb +102 -0
  16. data/lib/netzke/active_record/attributes.rb +100 -0
  17. data/lib/netzke/active_record/combobox_options.rb +43 -0
  18. data/lib/netzke/active_record/data_accessor.rb +9 -12
  19. data/lib/netzke/attributes_configurator.rb +195 -0
  20. data/lib/netzke/basic_app.rb +47 -4
  21. data/lib/netzke/configuration_panel.rb +1 -1
  22. data/lib/netzke/data_accessor.rb +7 -30
  23. data/lib/netzke/fields_configurator.rb +106 -41
  24. data/lib/netzke/form_panel.rb +28 -125
  25. data/lib/netzke/form_panel/form_panel_api.rb +2 -3
  26. data/lib/netzke/form_panel/form_panel_fields.rb +147 -0
  27. data/lib/netzke/form_panel/form_panel_js.rb +35 -15
  28. data/lib/netzke/grid_panel.rb +130 -213
  29. data/lib/netzke/grid_panel/grid_panel_api.rb +254 -257
  30. data/lib/netzke/grid_panel/grid_panel_columns.rb +226 -0
  31. data/lib/netzke/grid_panel/grid_panel_js.rb +126 -119
  32. data/lib/netzke/grid_panel/record_form_window.rb +7 -1
  33. data/lib/netzke/json_array_editor.rb +61 -0
  34. data/lib/netzke/plugins/configuration_tool.rb +1 -1
  35. data/lib/netzke/property_editor.rb +3 -3
  36. data/lib/netzke/search_panel.rb +164 -27
  37. data/lib/netzke/tab_panel.rb +14 -12
  38. data/stylesheets/basepack.css +43 -2
  39. data/test/app_root/app/models/book.rb +1 -1
  40. data/test/app_root/app/models/role.rb +3 -0
  41. data/test/app_root/app/models/user.rb +3 -0
  42. data/test/app_root/config/database.yml +1 -1
  43. data/test/app_root/db/migrate/20090102223630_create_netzke_field_lists.rb +18 -0
  44. data/test/app_root/db/migrate/20090423214303_create_roles.rb +11 -0
  45. data/test/app_root/db/migrate/20090423222114_create_users.rb +12 -0
  46. data/test/fixtures/books.yml +4 -2
  47. data/test/fixtures/categories.yml +2 -2
  48. data/test/fixtures/genres.yml +6 -6
  49. data/test/fixtures/roles.yml +8 -0
  50. data/test/fixtures/users.yml +11 -0
  51. data/test/test_helper.rb +2 -0
  52. data/test/unit/active_record_basepack_test.rb +2 -2
  53. data/test/unit/fields_configuration_test.rb +18 -0
  54. data/test/unit/grid_panel_test.rb +29 -27
  55. metadata +41 -16
  56. data/lib/app/models/netzke_auto_column.rb +0 -4
  57. data/lib/app/models/netzke_auto_field.rb +0 -4
  58. data/lib/app/models/netzke_auto_table.rb +0 -61
  59. data/lib/netzke/active_record/basepack.rb +0 -134
  60. data/lib/netzke/grid_panel/javascripts/filters.js +0 -7
  61. data/test/app_root/db/migrate/20090102223630_create_netzke_layouts.rb +0 -14
  62. data/test/unit/helper_model_test.rb +0 -30
@@ -36,7 +36,13 @@ module Netzke
36
36
  this.close();
37
37
  }
38
38
  END_OF_JAVASCRIPT
39
-
39
+
40
+ :on_test => <<-END_OF_JAVASCRIPT.l,
41
+ function(){
42
+ console.info("this.parent: ", this.getParent());
43
+ }
44
+ END_OF_JAVASCRIPT
45
+
40
46
  }
41
47
  end
42
48
  end
@@ -0,0 +1,61 @@
1
+ module Netzke
2
+ # Abstract GridPanel-based editor for a JSON array of homogenious objects.
3
+ # Inherit from it in order to override:
4
+ # <tt>store_data</p> - passes the data to be saved (e.g. to the persistant storage)
5
+ # <tt>initial_data</p> - should return initial data (e.g. from the persistant storage)
6
+ # For an example of an implementation, see Netzke::FieldsConfigurator.
7
+ class JsonArrayEditor < GridPanel
8
+ def initialize(*args)
9
+ super
10
+ data_class.configure(:owner => global_id, :columns => dynamic_fields, :initial_data => initial_data)
11
+ end
12
+
13
+ def data_class
14
+ NetzkePersistentArrayAutoModel
15
+ end
16
+
17
+ # Fields for NetzkePersistentArrayAutoModel (override it)
18
+ def dynamic_fields
19
+ default_columns.collect { |c| {:name => c[:name], :type => c[:attr_type], :default => c[:default_value]} }
20
+ end
21
+
22
+ # Default predifined columns (override if needed)
23
+ def default_columns
24
+ [{
25
+ :name => :id,
26
+ :attr_type => :integer
27
+ },{
28
+ :name => :name,
29
+ :attr_type => :string
30
+ },{
31
+ :name => :position,
32
+ :attr_type => :integer
33
+ }]
34
+ end
35
+
36
+ # Don't show the config tool
37
+ # def config_tool_needed?
38
+ # false
39
+ # end
40
+
41
+ def before_load
42
+ data_class.rebuild_table
43
+ super
44
+ end
45
+
46
+ private
47
+ # Override this
48
+ def store_data(data); end
49
+
50
+ # Override this
51
+ def initial_data
52
+ []
53
+ end
54
+
55
+ # This is an override of GridPanel#on_data_changed
56
+ def on_data_changed
57
+ store_data(data_class.all_columns)
58
+ end
59
+
60
+ end
61
+ end
@@ -22,7 +22,7 @@ module Netzke::Plugins
22
22
 
23
23
  # if you include ConfigurationTool, you are supposed to provide configuration_widgets method which will returns an array of arrgeratees
24
24
  # that will be included in the property window (each in its own tab or accordion pane)
25
- raise "configuration_widgets method undefined" unless base.instance_methods.include?("configuration_widgets")
25
+ raise "configuration_widgets method undefined" unless base.instance_methods.map(&:to_sym).include?(:configuration_widgets)
26
26
  end
27
27
 
28
28
  module ClassMethods
@@ -16,12 +16,12 @@ module Netzke
16
16
  {:restore_defaults => {:text => "Restore defaults"}}
17
17
  end
18
18
 
19
- def get_columns
19
+ def fields
20
20
  fields = @widget.class.property_fields
21
21
 
22
22
  for f in fields
23
23
  f[:value] = @widget.flat_config(f[:name]).nil? ? f[:default] : @widget.flat_config(f[:name])
24
- f[:xtype] = XTYPE_MAP[f[:type]]
24
+ f[:xtype] = xtype_for_attr_type(f[:type])
25
25
  f[:field_label] = f[:name].to_s.gsub("__", "/").humanize
26
26
  end
27
27
 
@@ -95,7 +95,7 @@ module Netzke
95
95
  end
96
96
 
97
97
  def normalize_form_value(value, field)
98
- case field[:type]
98
+ case field[:attr_type]
99
99
  when :boolean
100
100
  value.to_b
101
101
  when :json
@@ -5,7 +5,8 @@ module Netzke
5
5
  # Pretty much work in progress.
6
6
  class SearchPanel < FormPanel
7
7
 
8
- CONDITIONS = [:COMPARISON_CONDITIONS, :WILDCARD_CONDITIONS, :BOOLEAN_CONDITIONS].inject([]){|r, c| r + Searchlogic::NamedScopes::Conditions.const_get(c).keys} # Something like [:equals, :greater_than_or_equal_to, :does_not_equal, :less_than, :less_than_or_equal_to, :greater_than, :ends_with, :like, :begins_with, :empty, :null]
8
+ # Something like [:equals, :greater_than_or_equal_to, :does_not_equal, :less_than, :less_than_or_equal_to, :greater_than, :ends_with, :like, :begins_with, :empty, :null]
9
+ CONDITIONS = [:COMPARISON_CONDITIONS, :WILDCARD_CONDITIONS, :BOOLEAN_CONDITIONS].inject([]){|r, c| r + Searchlogic::NamedScopes::Conditions.const_get(c).keys}
9
10
 
10
11
  def default_config
11
12
  super.merge({
@@ -13,50 +14,186 @@ module Netzke
13
14
  })
14
15
  end
15
16
 
16
- def default_columns
17
- res = super
18
-
19
- res.map! do |f|
20
- norm_column = normalize_column(f)
21
- norm_column.merge!({
22
- :condition => "equals"
23
- })
24
- norm_column.merge!(:hidden => true) if norm_column[:name].to_s.index("__") || norm_column[:xtype] == :xcheckbox
17
+ def independent_config
18
+ super.deep_merge(
19
+ :ext_config => {
20
+ :tbar => ["Presets:",
21
+ {
22
+ :xtype => "combo",
23
+ :fieldLabel => "Presets",
24
+ :triggerAction => "all",
25
+ :store => (persistent_config[:saved_searches] || []).map{ |s| s["name"] },
26
+ :id => "presets-combo",
27
+ :listeners => {:before_select => {
28
+ :fn => "function(combo, record){Ext.getCmp('#{global_id}').selectPreset(record.data.field1);}".l
29
+ }}
30
+ }, :save, :del]
31
+ }
32
+ )
33
+ end
34
+
35
+ def actions
36
+ super.merge(
37
+ :save => {:text => "Save", :icon => "/images/icons/disk.png"},
38
+ :del => {:text => "Delete", :icon => "/images/icons/delete.png"}
39
+ )
40
+ end
41
+
42
+ def self.js_extend_properties
43
+ {
44
+ :remove_search_from_list => <<-END_OF_JAVASCRIPT.l,
45
+ function(name){
46
+ var presetsCombo = Ext.getCmp("presets-combo");
47
+ var presetsComboStore = presetsCombo.getStore();
48
+ presetsComboStore.removeAt(presetsComboStore.find('field1', name));
49
+ presetsCombo.reset();
50
+ this.getForm().reset();
51
+ }
52
+ END_OF_JAVASCRIPT
53
+
54
+ :set_values => <<-END_OF_JAVASCRIPT.l,
55
+ function(values){
56
+ this.getForm().setValues(Ext.decode(values));
57
+ }
58
+ END_OF_JAVASCRIPT
25
59
 
26
- norm_column
60
+ :select_preset => <<-END_OF_JAVASCRIPT.l,
61
+ function(name){
62
+ this.getForm().reset();
63
+ this.loadSearch({name: name});
64
+ }
65
+ END_OF_JAVASCRIPT
66
+
67
+ :on_save => <<-END_OF_JAVASCRIPT.l,
68
+ function(){
69
+ var searchName = Ext.getCmp("presets-combo").getValue();
70
+ if (searchName !== "") {
71
+ var presetsComboStore = Ext.getCmp("presets-combo").getStore();
72
+ if (presetsComboStore.find('field1', searchName) !== -1) {
73
+ Ext.Msg.confirm("Overwriting preset '" + searchName + "'", "Are you sure you want to overwrite this preset?", function(btn, text){
74
+ if (btn == 'yes') {
75
+ this.doSavePreset(searchName);
76
+ }
77
+ }, this);
78
+ } else {
79
+ this.doSavePreset(searchName);
80
+ presetsComboStore.add(new presetsComboStore.recordType({field1: searchName}));
81
+ }
82
+ }
83
+ }
84
+ END_OF_JAVASCRIPT
85
+
86
+ :do_save_preset => <<-END_OF_JAVASCRIPT.l,
87
+ function(name){
88
+ var values = this.getForm().getValues();
89
+ for (var k in values) {
90
+ if (values[k] == "") {delete values[k]}
91
+ }
92
+
93
+ this.saveSearch({
94
+ name: name,
95
+ values: Ext.encode(values)
96
+ });
97
+ }
98
+ END_OF_JAVASCRIPT
99
+
100
+ :on_del => <<-END_OF_JAVASCRIPT.l,
101
+ function(){
102
+ var searchName = Ext.getCmp("presets-combo").getValue();
103
+ if (searchName !== "") {
104
+ Ext.Msg.confirm("Deleting preset '" + searchName + "'", "Are you sure you want to delete this preset?", function(btn, text){
105
+ if (btn == 'yes') {
106
+ this.deleteSearch({
107
+ name: searchName
108
+ });
109
+ }
110
+ }, this);
111
+ }
112
+ }
113
+ END_OF_JAVASCRIPT
114
+
115
+ }
116
+ end
117
+
118
+ api :save_search
119
+ def save_search(params)
120
+ saved_searches = persistent_config[:saved_searches] || []
121
+ existing = saved_searches.detect{ |s| s["name"] == params[:name] }
122
+ values = ActiveSupport::JSON.decode(params[:values])
123
+ if existing
124
+ existing["values"].replace(values)
125
+ else
126
+ saved_searches << {"name" => params[:name], "values" => values}
27
127
  end
128
+ persistent_config[:saved_searches] = saved_searches
129
+ {:feedback => "Preset successfully saved"}
130
+ end
131
+
132
+ api :delete_search
133
+ def delete_search(params)
134
+ saved_searches = persistent_config[:saved_searches]
135
+ saved_searches.delete_if{ |s| s["name"] == params[:name] }
136
+ {:feedback => "Preset successfully deleted", :remove_search_from_list => params[:name]}
137
+ end
138
+
139
+ api :load_search
140
+ def load_search(params)
141
+ saved_searches = persistent_config[:saved_searches]
142
+ the_search = saved_searches.detect{ |s| s["name"] == params[:name] }
143
+
144
+ {:set_values => the_search["values"].to_json}
145
+ end
146
+
147
+ def initial_fields(only_included = true)
148
+ res = super
149
+
150
+ res.reject!{ |f| f[:virtual] }
28
151
 
152
+ res.each do |f|
153
+ f.merge!(:condition => "like", :default_value => nil)
154
+ f.merge!(:xtype => xtype_for_attr_type(:string), :attr_type => "string") if f[:name].to_s.index("__")
155
+ f.merge!(:condition => "greater_than") if [:datetime, :integer, :date].include?(f[:attr_type].to_sym)
156
+ f.merge!(:condition => "equals") if f["attr_type"] == "boolean"
157
+ end
158
+
29
159
  res
30
160
  end
31
161
 
32
162
  # columns to be displayed by the FieldConfigurator (which is GridPanel-based)
33
- def self.config_columns
34
- [
35
- {:name => :hidden, :type => :boolean, :editor => :checkbox, :width => 50},
36
- {:name => :name, :type => :string, :editor => :combobox},
37
- {:name => :condition, :type => :string, :editor => {:xtype => :combobox, :options => CONDITIONS}},
38
- {:name => :field_label, :type => :string},
39
- {:name => :xtype, :type => :string},
40
- {:name => :value, :type => :string},
163
+ def self.meta_columns
164
+ [
165
+ {:name => "hidden", :attr_type => :boolean, :editor => :checkbox, :width => 50},
166
+ {:name => "name", :attr_type => :string, :editor => :combobox},
167
+ {:name => "condition", :attr_type => :string, :editor => {:xtype => :combo, :store => CONDITIONS}},
168
+ {:name => "field_label", :attr_type => :string},
169
+ {:name => "xtype", :attr_type => :string},
170
+ {:name => "value", :attr_type => :string},
41
171
  ]
42
172
  end
43
173
 
44
174
  # tweaking the form fields at the last moment
45
175
  def js_config
46
176
  super.merge({
47
- :clmns => columns.map{ |c| c.merge({
48
- :field_label => "#{c[:field_label] || c[:name]} #{c[:condition]}".humanize,
177
+ :fields => fields.map{ |c| c.merge({
178
+ :label => "#{c[:label] || c[:name]} #{c[:condition]}".humanize,
49
179
  :name => "#{c[:name]}_#{c[:condition]}"
50
180
  })}
51
181
  })
52
182
  end
53
183
 
54
- # we need to correct the queries to cut off the condition suffixes, otherwise the FormPanel gets confused
55
- def get_combobox_options(params)
56
- column_name = params[:column]
57
- CONDITIONS.each { |c| column_name.sub!(/_#{c}$/, "") }
58
- super(:column => column_name)
59
- end
184
+ private
185
+ # we need to correct the queries to cut off the condition suffixes, otherwise the FormPanel gets confused
186
+ def get_combobox_options(params)
187
+ column_name = params[:column]
188
+ CONDITIONS.each { |c| column_name.sub!(/_#{c}$/, "") }
189
+ super(:column => column_name)
190
+ end
191
+
192
+ def attr_type_to_xtype_map
193
+ super.merge({
194
+ :boolean => :tricheckbox
195
+ })
196
+ end
60
197
 
61
198
  end
62
199
  end
@@ -28,23 +28,25 @@ module Netzke
28
28
 
29
29
  // We do this all in +render+ because only at this moment the activeTab is actually activated
30
30
  var activeTab = this.getActiveTab();
31
- this.loadWidgetInto(activeTab);
31
+
32
+ // Insert (render) preloaded widgets into their respective tabs
33
+ this.items.each(function(fitPanel){
34
+ var preloadedItemConfig = this[fitPanel.widget.camelize(true)+"Config"];
35
+ if (preloadedItemConfig){
36
+ var klass = this.classifyScopedName(preloadedItemConfig.scopedClassName);
37
+ fitPanel.add(new klass(preloadedItemConfig));
38
+ }
39
+ }, this);
40
+
41
+ // setting the tabchange event
32
42
  this.on('tabchange', this.onTabChange, this);
33
43
  }
34
44
  END_OF_JAVASCRIPT
35
45
 
46
+ # loads widget from the server
36
47
  :load_widget_into => <<-END_OF_JAVASCRIPT.l,
37
48
  function(fitPanel){
38
- var preloadedItemConfig = this[fitPanel.widget.camelize(true)+"Config"];
39
- if (preloadedItemConfig){
40
- // preloaded widget only needs to be instantiated, as its class and configuration have already been loaded
41
- var klass = this.classifyScopedName(preloadedItemConfig.scopedClassName);
42
- fitPanel.add(new klass(preloadedItemConfig));
43
- fitPanel.doLayout();
44
- } else {
45
- // load the widget from the server
46
- this.loadAggregatee({id:fitPanel.widget, container:fitPanel.id});
47
- }
49
+ this.loadAggregatee({id:fitPanel.widget, container:fitPanel.id});
48
50
  }
49
51
  END_OF_JAVASCRIPT
50
52
 
@@ -78,7 +80,7 @@ module Netzke
78
80
 
79
81
  :on_tab_change => <<-END_OF_JAVASCRIPT.l
80
82
  function(self, tab) {
81
- // load widget into the panel if it wasn't loaded yet
83
+ // load widget into the panel from the server if it's not there yet
82
84
  if (!tab.getWidget()) {
83
85
  this.loadWidgetInto(tab);
84
86
  }
@@ -1,5 +1,5 @@
1
1
  /* Fix for Ext's EditableItem render problem (from http://www.extjs.com/forum/showthread.php?p=290267#post290267) */
2
- .x-menu div.x-menu-item .x-menu-item-icon {
2
+ /*.x-menu div.x-menu-item .x-menu-item-icon {
3
3
  left: 0px;
4
4
  margin-top: 0px !important;
5
5
  position:relative;
@@ -14,7 +14,7 @@ left: 3px;
14
14
  .ext-ie6 .x-menu-item-icon {
15
15
  left: -24px;
16
16
  }
17
-
17
+ */
18
18
  /* write accordion header in bold */
19
19
  .x-accordion-hd {
20
20
  font-weight:bold;
@@ -28,4 +28,45 @@ left: -24px;
28
28
  /* StatusBar - fixing forgotten "!important" */
29
29
  .x-statusbar .x-status-busy {
30
30
  padding-left: 25px !important;
31
+ }
32
+
33
+ /* Tri-state checkbox */
34
+ .x-form-tscheckbox {
35
+ height: 13px;
36
+ width: 13px;
37
+ background: url('basepack/ts-checkbox.gif') no-repeat 0 0;
38
+ vertical-align: bottom;
39
+ }
40
+ .x-checkbox-checked .x-form-tscheckbox {
41
+ background-position:0 -13px;
42
+ }
43
+ .x-checkbox-undef .x-form-tscheckbox {
44
+ background-position:0 -26px;
45
+ }
46
+ .x-item-disabled .x-form-tscheckbox {
47
+ background-position:-39px 0;
48
+ }
49
+ .x-form-check-over .x-form-tscheckbox {
50
+ background-position: -13px 0;
51
+ }
52
+ .x-form-check-down .x-form-tscheckbox {
53
+ background-position:-26px 0;
54
+ }
55
+ .x-checkbox-checked .x-form-check-over .x-form-tscheckbox {
56
+ background-position:-13px -13px;
57
+ }
58
+ .x-checkbox-checked .x-form-check-down .x-form-tscheckbox {
59
+ background-position:-26px -13px;
60
+ }
61
+ .x-checkbox-checked.x-item-disabled .x-form-tscheckbox {
62
+ background-position:-39px -13px;
63
+ }
64
+ .x-checkbox-undef .x-form-check-over .x-form-tscheckbox {
65
+ background-position:-13px -26px;
66
+ }
67
+ .x-checkbox-undef .x-form-check-down .x-form-tscheckbox {
68
+ background-position:-26px -26px;
69
+ }
70
+ .x-checkbox-undef.x-item-disabled .x-form-tscheckbox {
71
+ background-position:-39px -26px;
31
72
  }
@@ -1,7 +1,7 @@
1
1
  class Book < ActiveRecord::Base
2
2
  belongs_to :genre
3
3
 
4
- # netzke_virtual_attribute :recent => {:read_only => true}
4
+ netzke_attribute :recent
5
5
 
6
6
  def recent
7
7
  updated_at > 1.hour.ago ? "Yes" : "No"