netzke-basepack 0.4.2 → 0.5.1

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/.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