netzke-basepack 0.4.2 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. data/.autotest +1 -0
  2. data/.gitignore +6 -0
  3. data/{CHANGELOG → CHANGELOG.rdoc} +26 -0
  4. data/README.rdoc +11 -11
  5. data/Rakefile +37 -11
  6. data/TODO.rdoc +8 -0
  7. data/VERSION +1 -0
  8. data/javascripts/basepack.js +71 -28
  9. data/lib/app/models/netzke_auto_column.rb +56 -0
  10. data/lib/netzke-basepack.rb +5 -3
  11. data/lib/netzke/accordion_panel.rb +69 -67
  12. data/lib/netzke/active_record/basepack.rb +104 -0
  13. data/lib/netzke/active_record/data_accessor.rb +33 -0
  14. data/lib/netzke/basic_app.rb +233 -124
  15. data/lib/netzke/border_layout_panel.rb +97 -98
  16. data/lib/netzke/configuration_panel.rb +24 -0
  17. data/lib/netzke/data_accessor.rb +71 -0
  18. data/lib/netzke/ext.rb +6 -0
  19. data/lib/netzke/field_model.rb +1 -1
  20. data/lib/netzke/fields_configurator.rb +62 -37
  21. data/lib/netzke/form_panel.rb +161 -51
  22. data/lib/netzke/form_panel_api.rb +74 -0
  23. data/lib/netzke/form_panel_js.rb +129 -0
  24. data/lib/netzke/grid_panel.rb +385 -80
  25. data/lib/netzke/grid_panel_api.rb +352 -0
  26. data/lib/netzke/grid_panel_extras/javascripts/rows-dd.js +280 -0
  27. data/lib/netzke/grid_panel_js.rb +721 -0
  28. data/lib/netzke/masquerade_selector.rb +53 -0
  29. data/lib/netzke/panel.rb +9 -0
  30. data/lib/netzke/plugins/configuration_tool.rb +121 -0
  31. data/lib/netzke/property_editor.rb +95 -7
  32. data/lib/netzke/property_editor_extras/helper_model.rb +55 -34
  33. data/lib/netzke/search_panel.rb +62 -0
  34. data/lib/netzke/tab_panel.rb +97 -37
  35. data/lib/netzke/table_editor.rb +49 -44
  36. data/lib/netzke/tree_panel.rb +15 -16
  37. data/lib/netzke/wrapper.rb +29 -5
  38. data/netzke-basepack.gemspec +151 -19
  39. data/stylesheets/basepack.css +5 -0
  40. data/test/app_root/app/models/book.rb +1 -1
  41. data/test/app_root/db/migrate/20081222035855_create_netzke_preferences.rb +1 -1
  42. data/test/unit/accordion_panel_test.rb +1 -2
  43. data/test/unit/active_record_basepack_test.rb +54 -0
  44. data/test/unit/grid_panel_test.rb +8 -12
  45. data/test/unit/helper_model_test.rb +30 -0
  46. metadata +69 -78
  47. data/Manifest +0 -86
  48. data/TODO +0 -3
  49. data/lib/app/models/netzke_hash_record.rb +0 -180
  50. data/lib/app/models/netzke_layout_item.rb +0 -11
  51. data/lib/netzke/ar_ext.rb +0 -269
  52. data/lib/netzke/configuration_tool.rb +0 -80
  53. data/lib/netzke/container.rb +0 -77
  54. data/lib/netzke/db_fields.rb +0 -44
  55. data/lib/netzke/fields_configurator_old.rb +0 -62
  56. data/lib/netzke/form_panel_extras/interface.rb +0 -56
  57. data/lib/netzke/form_panel_extras/js_builder.rb +0 -134
  58. data/lib/netzke/grid_panel_extras/interface.rb +0 -206
  59. data/lib/netzke/grid_panel_extras/js_builder.rb +0 -352
  60. data/test/unit/ar_ext_test.rb +0 -53
  61. data/test/unit/netzke_hash_record_test.rb +0 -52
  62. data/test/unit/netzke_layout_item_test.rb +0 -28
@@ -0,0 +1,53 @@
1
+ module Netzke
2
+ class MasqueradeSelector < TabPanel
3
+
4
+ def items
5
+ @items ||= [{
6
+ :name => "roles",
7
+ :active => true,
8
+ :widget_class_name => "GridPanel",
9
+ :data_class_name => 'Role',
10
+ :columns => [:id, :name],
11
+ :ext_config => {
12
+ :header => false,
13
+ :bbar => ['search']
14
+ }
15
+ },{
16
+ :name => "users",
17
+ :preloaded => true,
18
+ :widget_class_name => "GridPanel",
19
+ :data_class_name => 'User',
20
+ :ext_config => {
21
+ :header => false,
22
+ :rows_per_page => 10,
23
+ :bbar => ['search']
24
+ },
25
+ :columns => [:id, :login]
26
+ }]
27
+ end
28
+
29
+ def self.js_extend_properties
30
+ {
31
+ :after_constructor => <<-END_OF_JAVASCRIPT.l,
32
+ function(){
33
+ this.items.each(function(tab){
34
+ tab.on('add', function(ct, cmp){
35
+ cmp.on('rowclick', this.rowclickHandler, this);
36
+ }, this);
37
+ }, this);
38
+ }
39
+ END_OF_JAVASCRIPT
40
+
41
+ :rowclick_handler => <<-END_OF_JAVASCRIPT.l
42
+ function(grid, rowIndex, e){
43
+ var mode = grid.id.split("__").pop();
44
+ var normMode = mode === 'users' ? 'user' : 'role';
45
+ this.masquerade = {};
46
+ this.masquerade[normMode] = grid.store.getAt(rowIndex).get('id');
47
+ }
48
+ END_OF_JAVASCRIPT
49
+ }
50
+ end
51
+
52
+ end
53
+ end
data/lib/netzke/panel.rb CHANGED
@@ -1,4 +1,13 @@
1
1
  module Netzke
2
2
  class Panel < Base
3
+ def self.js_extend_properties
4
+ {
5
+ :update_body_html => <<-END_OF_JAVASCRIPT.l,
6
+ function(html){
7
+ this.body.update(html);
8
+ }
9
+ END_OF_JAVASCRIPT
10
+ }
11
+ end
3
12
  end
4
13
  end
@@ -0,0 +1,121 @@
1
+ module Netzke::Plugins
2
+ # Include this module into any widget where you want a "gear" tool button in the top toolbar
3
+ # which will triggger a modal window, which will load the ConfigurationPanel TabPanel-based
4
+ # widget, which in its turn will contain all the aggregatees specified in widget's "configuration_widgets"
5
+ # method (which *must* be defined)
6
+ module ConfigurationTool
7
+ def self.included(base)
8
+ base.extend ClassMethods
9
+
10
+ base.class_eval do
11
+ # replacing instance methods
12
+ [:config, :initial_aggregatees, :js_config].each{ |m| alias_method_chain m, :config_tool }
13
+
14
+ # replacing class methods
15
+ class << self
16
+ alias_method_chain :js_extend_properties, :config_tool
17
+ end
18
+
19
+ # API to commit the changes
20
+ api :commit
21
+ end
22
+
23
+ # if you include ConfigurationTool, you are supposed to provide configuration_widgets method which will returns an array of arrgeratees
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")
26
+ end
27
+
28
+ module ClassMethods
29
+ def js_extend_properties_with_config_tool
30
+ js_extend_properties_without_config_tool.merge({
31
+ :gear => <<-END_OF_JAVASCRIPT.l
32
+ function(){
33
+ var w = new Ext.Window({
34
+ title:'Config - '+ this.dataClassName,
35
+ layout:'fit',
36
+ modal:true,
37
+ width: Ext.lib.Dom.getViewWidth() *0.9,
38
+ height: Ext.lib.Dom.getViewHeight() *0.9,
39
+ closeAction:'destroy',
40
+ buttons:[{
41
+ text:'OK',
42
+ disabled: !this.configurable,
43
+ tooltip: this.configurable ? null : "No dynamic configuration for this component",
44
+ handler:function(){
45
+ w.closeRes = 'OK';
46
+ w.close();
47
+ }
48
+ },{
49
+ text:'Cancel',
50
+ handler:function(){
51
+ w.closeRes = 'cancel';
52
+ w.close();
53
+ }
54
+ }]
55
+
56
+ });
57
+
58
+ w.show(null, function(){
59
+ this.loadAggregatee({id:"configuration_panel", container:w.id});
60
+ }, this);
61
+
62
+ w.on('close', function(){
63
+ if (w.closeRes == 'OK'){
64
+ var configurationPanel = this.getChildWidget('configuration_panel');
65
+ var panels = configurationPanel.getLoadedChildren();
66
+ var commitData = {};
67
+ Ext.each(panels, function(p){
68
+ if (p.getCommitData) {commitData[p.localId(configurationPanel)] = p.getCommitData();}
69
+ }, this);
70
+ configurationPanel.commit({commit_data:Ext.encode(commitData)});
71
+ }
72
+ }, this);
73
+ }
74
+ END_OF_JAVASCRIPT
75
+ })
76
+ end
77
+ end
78
+
79
+ def config_with_config_tool
80
+ orig_config = config_without_config_tool
81
+ return orig_config unless config_tool_needed?
82
+ orig_config.deep_merge({
83
+ :ext_config => {
84
+ :tools => orig_config[:ext_config][:tools].clone << "gear",
85
+ :header => true
86
+ }
87
+ })
88
+ end
89
+
90
+ def initial_aggregatees_with_config_tool
91
+ res = initial_aggregatees_without_config_tool
92
+
93
+ # Add the ConfigurationPanel as aggregatee, which in its turn aggregates widgets from the
94
+ # configuration_widgets method
95
+ res.merge!(:configuration_panel => {
96
+ :widget_class_name => 'ConfigurationPanel',
97
+ :items => configuration_widgets,
98
+ :late_aggregation => true
99
+ }) if config_tool_needed?
100
+
101
+ res
102
+ end
103
+
104
+ def tools_with_config_tool
105
+ tools = tools_without_config_tool
106
+ # Add the toolbutton
107
+ tools << 'gear' if config_tool_needed?
108
+ tools
109
+ end
110
+
111
+ def js_config_with_config_tool
112
+ orig_config = js_config_without_config_tool
113
+ orig_config.merge(:configurable => config[:persistent_config])
114
+ end
115
+
116
+ def config_tool_needed?
117
+ config_without_config_tool[:ext_config][:config_tool] || config_without_config_tool[:ext_config][:mode] == :config
118
+ end
119
+
120
+ end
121
+ end
@@ -3,15 +3,103 @@ module Netzke
3
3
 
4
4
  def initialize(*args)
5
5
  super
6
- PropertyEditorExtras::HelperModel.widget_name = config[:widget_name]
7
- config[:data_class_name] = "Netzke::PropertyEditorExtras::HelperModel"
8
- config[:record] = PropertyEditorExtras::HelperModel.new
9
- config[:bbar] = %w{ apply }
10
- config[:persistent_layout] = false
6
+ @widget = @passed_config[:widget]
7
+ end
8
+
9
+ def independent_config
10
+ res = super
11
+ res[:ext_config][:bbar] = %w{ restore_defaults }
12
+ res
13
+ end
14
+
15
+ def actions
16
+ {:restore_defaults => {:text => "Restore defaults"}}
11
17
  end
12
18
 
13
- def self.js_base_class
14
- FormPanel
19
+ def get_columns
20
+ fields = @widget.class.property_fields
21
+
22
+ for f in fields
23
+ f[:value] = @widget.flat_config(f[:name]).nil? ? f[:default] : @widget.flat_config(f[:name])
24
+ f[:xtype] = XTYPE_MAP[f[:type]]
25
+ f[:field_label] = f[:name].to_s.gsub("__", "/").humanize
26
+ end
27
+
28
+ fields
29
+ end
30
+
31
+ def self.js_extend_properties
32
+ {
33
+ :label_width => 200,
34
+
35
+ # Disable the 'gear' tool for now
36
+ :gear => <<-END_OF_JAVASCRIPT.l,
37
+ function(){
38
+ this.feedback("You can't configure property editor (yet)");
39
+ }
40
+ END_OF_JAVASCRIPT
41
+
42
+ :restore_defaults => <<-END_OF_JAVASCRIPT.l,
43
+ function(){
44
+ this.restoreDefaults();
45
+ }
46
+ END_OF_JAVASCRIPT
47
+
48
+ :get_commit_data => <<-END_OF_JAVASCRIPT.l
49
+ function(){
50
+ if (!this.getForm().isValid()) {this.getForm().reset()}; // if some fields are invalid, don't send anything
51
+ var values = this.getForm().getValues();
52
+ for (var k in values) {
53
+ if (values[k] == "") {values[k] = null}
54
+ }
55
+ return values;
56
+ }
57
+ END_OF_JAVASCRIPT
58
+ }
15
59
  end
60
+
61
+ api :restore_defaults
62
+ def restore_defaults(params)
63
+ values = []
64
+ columns.each do |c|
65
+ init_config = @widget.flat_initial_config.detect{ |ic| ic[:name] == c[:name] }
66
+
67
+ if init_config.nil?
68
+ property_fields ||= @widget.class.property_fields
69
+ values << property_fields.detect{ |f| f[:name] == c[:name] }[:default]
70
+ else
71
+ values << init_config[:value]
72
+ end
73
+
74
+ end
75
+ {:set_form_values => values}
76
+ end
77
+
78
+ def commit(data)
79
+ fields = @widget.class.property_fields
80
+ data.each_pair do |property, value|
81
+ field = fields.detect{ |f| f[:name] == property.to_sym }
82
+ # default = @widget.config[property].nil? ? field[:default] : @widget.config[property]
83
+ default = @widget.flat_initial_config(property).nil? ? field[:default] : @widget.flat_initial_config(property)
84
+ # Only store the value in persistent config when it's different from the default one
85
+ if field[:type] == :boolean
86
+ # handle boolean type separately
87
+ value = value.to_b
88
+ @widget.persistent_config[property] = value ^ default ? value : nil
89
+ else
90
+ if field[:type] == :json
91
+ value = ActiveSupport::JSON.decode(value)
92
+ end
93
+
94
+ # Empty string means "null" for now...
95
+ # value = nil if value.blank?
96
+ # logger.debug "!!! value: #{value.inspect}\n"
97
+
98
+ @widget.persistent_config[property] = default == value ? nil : value
99
+ end
100
+ end
101
+ {}
102
+ end
103
+
16
104
  end
17
105
  end
@@ -1,18 +1,27 @@
1
1
  module Netzke
2
2
  module PropertyEditorExtras
3
3
  class HelperModel
4
- def self.widget_name=(name)
5
- @@widget_name = name
4
+ def self.widget=(w)
5
+ @@widget = w
6
6
  end
7
-
8
- def self.exposed_columns
9
- # preferences = NetzkePreference.find_all_by_widget_name_and_user_id(@@widget_name, Netzke::Base.user && Netzke::Base.user.id)
10
- preferences = NetzkePreference.find_all_for_widget(@@widget_name)
11
- preferences.map{|p| {
12
- :name => p.name,
13
- :field_label => p.name.gsub('__', "/").humanize,
14
- :type => p.pref_type.to_sym
15
- }}
7
+
8
+ def self.widget
9
+ @@widget ||= raise RuntimeError, "No widget specified for PropertyEditorExtras::HelperModel"
10
+ end
11
+
12
+ def self.reflect_on_all_associations
13
+ []
14
+ end
15
+
16
+ def self.primary_key
17
+ "id"
18
+ end
19
+
20
+ def self.netzke_exposed_attributes
21
+ preferences = self.widget.flat_default_config
22
+ # preferences = NetzkePreference.find_all_for_widget(widget.name)
23
+ preferences.each { |p| p.reject!{ |k,v| k == :value}.merge!(:field_label => p[:name].to_s.gsub('__', "/").humanize) }
24
+ preferences
16
25
  end
17
26
 
18
27
  DEFAULTS_FOR_FIELD = {
@@ -57,7 +66,7 @@ module Netzke
57
66
  a
58
67
  end
59
68
 
60
- def to_array(columns)
69
+ def to_array(columns, widget = nil)
61
70
  res = []
62
71
  for c in columns
63
72
  method = c.is_a?(Symbol) ? c : c[:name]
@@ -67,36 +76,48 @@ module Netzke
67
76
  res
68
77
  end
69
78
 
79
+ # somewhat sofisticated code to convert all NetzkePreferences for current widget into a hash ("un-flatten")
80
+ def attributes
81
+ prefs = NetzkePreference.find_all_for_widget(self.class.widget.id_name)
82
+ res = {}
83
+ prefs.each do |p|
84
+ tmp_res = {}
85
+ hsh_levels = p.name.split("__").map(&:to_sym)
86
+ hsh_levels.each do |level_prefix|
87
+ tmp_res[level_prefix] ||= level_prefix == hsh_levels.last ? p.normalized_value : {}
88
+ res[level_prefix] = tmp_res[level_prefix] if level_prefix == hsh_levels.first
89
+ tmp_res = tmp_res[level_prefix]
90
+ end
91
+ end
92
+ res
93
+ end
94
+
70
95
  def method_missing(method_name, *args)
96
+ # Rails.logger.debug "!!! method_name: #{method_name.inspect}"
71
97
  method_name = method_name.to_s
72
98
  method_name_without_equal_sign = method_name.sub(/=$/, '')
73
- NetzkePreference.widget_name = @@widget_name
99
+ NetzkePreference.widget_name = self.class.widget.id_name
74
100
 
75
101
  if method_name =~ /=$/
76
- current_value = NetzkePreference[method_name_without_equal_sign]
77
- new_value = args.first
78
-
79
- if new_value.blank?
80
- new_value = nil
81
- else
82
- if current_value.is_a?(Array) || current_value.is_a?(Hash)
83
- begin
84
- # JSON-parse if we expect an Array on Hash
85
- new_value = ActiveSupport::JSON.decode(new_value)
86
- rescue JSON::ParserError
87
- new_value = current_value # TODO: provide nice feedback about JSON parsing failed
88
- end
89
- elsif current_value.is_a?(TrueClass) || current_value.is_a?(FalseClass)
90
- # convert to true/false if expecting a Boolean
91
- new_value = {"false" => false, "true" => true}[new_value]
92
- else
93
- new_value = new_value.send(NetzkePreference::ELEMENTARY_CONVERTION_METHODS[current_value.class.name])
94
- end
102
+ # current_value = NetzkePreference[method_name_without_equal_sign] # may be nil
103
+ current_value = self.class.widget.flat_independent_config(method_name_without_equal_sign)
104
+
105
+ begin
106
+ new_value = ActiveSupport::JSON.decode(args.first) # TODO: provide feedback about this error
107
+ rescue ActiveSupport::JSON::ParseError
108
+ new_value = current_value
95
109
  end
96
-
110
+
111
+ # default_value = self.class.widget.flat_default_config(method_name_without_equal_sign)
112
+ initial_value = self.class.widget.flat_initial_config(method_name_without_equal_sign)
113
+
114
+ new_value = nil if new_value == initial_value
115
+ # Rails.logger.debug "!!! new_value: #{new_value.inspect}"
97
116
  NetzkePreference[method_name_without_equal_sign] = new_value
98
117
  else
99
- NetzkePreference[method_name_without_equal_sign]
118
+ res = self.class.widget.flat_independent_config(method_name_without_equal_sign)
119
+ res = ActiveSupport::JSON.encode(res) if res.is_a?(Array) || res.is_a?(Hash)
120
+ res
100
121
  end
101
122
  end
102
123
 
@@ -0,0 +1,62 @@
1
+ module Netzke
2
+ # SearchPanel
3
+ #
4
+ # FormPanel-based widget that allows create configurable searchlogic-compatible searches.
5
+ # Pretty much work in progress.
6
+ class SearchPanel < FormPanel
7
+ # 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
+ CONDITIONS = [:COMPARISON_CONDITIONS, :WILDCARD_CONDITIONS, :BOOLEAN_CONDITIONS].inject([]){|r, c| r + Searchlogic::NamedScopes::Conditions.const_get(c).keys}
9
+
10
+ def default_config
11
+ super.merge({
12
+ :data_class_name => @passed_config[:search_class_name]
13
+ })
14
+ end
15
+
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("__")
25
+
26
+ norm_column
27
+ end
28
+
29
+ res
30
+ end
31
+
32
+ # 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},
41
+ ]
42
+ end
43
+
44
+ # tweaking the form fields at the last moment
45
+ def js_config
46
+ super.merge({
47
+ :clmns => columns.map{ |c| c.merge({
48
+ :field_label => "#{c[:field_label] || c[:name]} #{c[:condition]}".humanize,
49
+ :name => "#{c[:name]}_#{c[:condition]}"
50
+ })}
51
+ })
52
+ end
53
+
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
60
+
61
+ end
62
+ end