skozlov-netzke-basepack 0.1.1.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. data/.autotest +1 -0
  2. data/.gitignore +5 -0
  3. data/LICENSE +2 -19
  4. data/README.rdoc +87 -0
  5. data/Rakefile +28 -12
  6. data/TODO.rdoc +7 -0
  7. data/VERSION +1 -0
  8. data/autotest/discover.rb +3 -0
  9. data/init.rb +0 -1
  10. data/javascripts/basepack.js +839 -49
  11. data/lib/app/models/netzke_auto_column.rb +56 -0
  12. data/lib/netzke/accordion_panel.rb +113 -0
  13. data/lib/netzke/active_record/basepack.rb +104 -0
  14. data/lib/netzke/active_record/data_accessor.rb +21 -0
  15. data/lib/netzke/basic_app.rb +325 -0
  16. data/lib/netzke/border_layout_panel.rb +128 -0
  17. data/lib/netzke/configuration_panel.rb +24 -0
  18. data/lib/netzke/data_accessor.rb +71 -0
  19. data/lib/netzke/ext.rb +6 -0
  20. data/lib/netzke/field_model.rb +131 -0
  21. data/lib/netzke/fields_configurator.rb +95 -0
  22. data/lib/netzke/form_panel.rb +214 -0
  23. data/lib/netzke/form_panel_api.rb +74 -0
  24. data/lib/netzke/form_panel_extras/javascripts/xcheckbox.js +82 -0
  25. data/lib/netzke/form_panel_js.rb +129 -0
  26. data/lib/netzke/grid_panel.rb +442 -0
  27. data/lib/netzke/grid_panel_api.rb +352 -0
  28. data/lib/netzke/grid_panel_extras/javascripts/check-column.js +33 -0
  29. data/{javascripts → lib/netzke/grid_panel_extras/javascripts}/filters.js +0 -0
  30. data/lib/netzke/grid_panel_extras/javascripts/rows-dd.js +280 -0
  31. data/lib/netzke/grid_panel_js.rb +721 -0
  32. data/lib/netzke/panel.rb +13 -0
  33. data/lib/netzke/plugins/configuration_tool.rb +121 -0
  34. data/lib/netzke/property_editor.rb +105 -0
  35. data/lib/netzke/property_editor_extras/helper_model.rb +126 -0
  36. data/lib/netzke/search_panel.rb +62 -0
  37. data/lib/netzke/tab_panel.rb +160 -0
  38. data/lib/netzke/table_editor.rb +118 -0
  39. data/lib/netzke/tree_panel.rb +73 -0
  40. data/lib/netzke/wrapper.rb +42 -0
  41. data/lib/netzke-basepack.rb +9 -15
  42. data/netzke-basepack.gemspec +147 -20
  43. data/stylesheets/basepack.css +26 -0
  44. data/test/app_root/app/models/book.rb +1 -1
  45. data/test/app_root/config/environment.rb +1 -0
  46. data/test/app_root/db/migrate/20081222033440_create_genres.rb +1 -0
  47. data/test/app_root/db/migrate/20081222035855_create_netzke_preferences.rb +1 -1
  48. data/test/app_root/db/migrate/20090102223630_create_netzke_layouts.rb +14 -0
  49. data/test/app_root/vendor/plugins/acts_as_list/README +23 -0
  50. data/test/app_root/vendor/plugins/acts_as_list/init.rb +3 -0
  51. data/test/app_root/vendor/plugins/acts_as_list/lib/active_record/acts/list.rb +256 -0
  52. data/test/test_helper.rb +1 -2
  53. data/test/unit/accordion_panel_test.rb +20 -0
  54. data/test/unit/active_record_basepack_test.rb +54 -0
  55. data/test/unit/grid_panel_test.rb +43 -0
  56. data/test/unit/helper_model_test.rb +30 -0
  57. data/test/unit/netzke_basepack_test.rb +4 -0
  58. data/test/unit/tab_panel_test.rb +21 -0
  59. metadata +96 -72
  60. data/CHANGELOG +0 -14
  61. data/Manifest +0 -65
  62. data/README.mdown +0 -18
  63. data/generators/netzke_basepack/USAGE +0 -8
  64. data/generators/netzke_basepack/netzke_basepack_generator.rb +0 -8
  65. data/generators/netzke_basepack/netzke_grid_generator.rb +0 -7
  66. data/generators/netzke_basepack/templates/create_netzke_grid_columns.rb +0 -21
  67. data/lib/app/models/netzke_grid_column.rb +0 -23
  68. data/lib/netzke/accordion.rb +0 -11
  69. data/lib/netzke/ar_ext.rb +0 -163
  70. data/lib/netzke/column.rb +0 -43
  71. data/lib/netzke/container.rb +0 -81
  72. data/lib/netzke/grid.rb +0 -120
  73. data/lib/netzke/grid_interface.rb +0 -156
  74. data/lib/netzke/grid_js_builder.rb +0 -276
  75. data/lib/netzke/preference_grid.rb +0 -43
  76. data/lib/netzke/properties_tool.rb +0 -66
  77. data/lib/netzke/property_grid.rb +0 -60
  78. data/test/ar_ext_test.rb +0 -39
  79. data/test/column_test.rb +0 -27
  80. data/test/grid_test.rb +0 -43
  81. data/test/netzke_basepack_test.rb +0 -8
@@ -0,0 +1,214 @@
1
+ module Netzke
2
+ # = FormPanel
3
+ #
4
+ # Represents Ext.form.FormPanel
5
+ #
6
+ # == Configuration
7
+ # * <tt>:data_class_name</tt> - name of the ActiveRecord model that provides data to this GridPanel.
8
+ # * <tt>:record</tt> - record to be displayd in the form. Takes precedence over <tt>:record_id</tt>
9
+ # * <tt>:record_id</tt> - id of the record to be displayd in the form. Also see <tt>:record</tt>
10
+ #
11
+ # In the <tt>:ext_config</tt> hash (see Netzke::Base) the following FormPanel specific options are available:
12
+ #
13
+ # * <tt>:mode</tt> - when set to <tt>:config</tt>, FormPanel loads in configuration mode
14
+ class FormPanel < Base
15
+ include Netzke::FormPanelJs # javascript (client-side)
16
+ include Netzke::FormPanelApi # API (server-side)
17
+ include Netzke::DataAccessor # some code shared between GridPanel, FormPanel, and other widgets that use database attributes
18
+
19
+ # Class-level configuration with defaults
20
+ def self.config
21
+ set_default_config({
22
+ :config_tool_available => true,
23
+
24
+ :default_config => {
25
+ :ext_config => {
26
+ :bbar => %w{ apply },
27
+ :tools => %w{ }
28
+ },
29
+ :persistent_config => false
30
+ }
31
+ })
32
+ end
33
+
34
+ # Extra javascripts
35
+ def self.include_js
36
+ [
37
+ "#{File.dirname(__FILE__)}/form_panel_extras/javascripts/xcheckbox.js"
38
+ ]
39
+ end
40
+
41
+ api :submit, :load, :get_combobox_options
42
+
43
+ attr_accessor :record
44
+
45
+ def initialize(*args)
46
+ super
47
+ apply_helpers
48
+ @record = config[:record] || data_class && data_class.find_by_id(config[:record_id])
49
+ end
50
+
51
+ def data_class
52
+ @data_class ||= config[:data_class_name] && config[:data_class_name].constantize
53
+ end
54
+
55
+ def configuration_widgets
56
+ res = []
57
+
58
+ res << {
59
+ :name => 'fields',
60
+ :widget_class_name => "FieldsConfigurator",
61
+ :active => true,
62
+ :widget => self,
63
+ :persistent_config => true
64
+ }
65
+
66
+ res << {
67
+ :name => 'general',
68
+ :widget_class_name => "PropertyEditor",
69
+ :widget => self,
70
+ :ext_config => {:title => false}
71
+ }
72
+
73
+ res
74
+ end
75
+
76
+ def actions
77
+ {
78
+ :apply => {:text => 'Apply'}
79
+ }
80
+ end
81
+
82
+ def columns
83
+ @columns ||= get_columns.convert_keys{|k| k.to_sym}
84
+ end
85
+
86
+ # parameters used to instantiate the JS object
87
+ def js_config
88
+ res = super
89
+ res.merge!(:clmns => columns)
90
+ res.merge!(:data_class_name => config[:data_class_name])
91
+ res
92
+ end
93
+
94
+ # columns to be displayed by the FieldConfigurator (which is GridPanel-based)
95
+ def self.config_columns
96
+ [
97
+ {:name => :name, :type => :string, :editor => :combobox, :width => 200},
98
+ {:name => :hidden, :type => :boolean, :editor => :checkbox, :width => 40, :header => "Excl"},
99
+ {:name => :disabled, :type => :boolean, :editor => :checkbox, :width => 40, :header => "Dis"},
100
+ {:name => :xtype, :type => :string},
101
+ {:name => :value, :type => :string},
102
+ {:name => :field_label, :type => :string},
103
+ {:name => :input_type, :type => :string}
104
+ ]
105
+ end
106
+
107
+ def self.property_fields
108
+ res = [
109
+ {:name => :ext_config__title, :type => :string},
110
+ {:name => :ext_config__header, :type => :boolean, :default => true}
111
+ # {:name => :ext_config__bbar, :type => :json},
112
+ # {:name => :ext_config__prohibit_create, :type => :boolean},
113
+ # {:name => :ext_config__prohibit_update, :type => :boolean},
114
+ # {:name => :ext_config__prohibit_delete, :type => :boolean},
115
+ # {:name => :ext_config__prohibit_read, :type => :boolean}
116
+ ]
117
+
118
+ res
119
+
120
+ end
121
+
122
+ # Normalized columns
123
+ def normalized_columns
124
+ @normalized_columns ||= normalize_columns(columns)
125
+ end
126
+
127
+
128
+ def get_columns
129
+ if config[:persistent_config]
130
+ persistent_config['layout__columns'] ||= default_columns
131
+ res = normalize_array_of_columns(persistent_config['layout__columns'])
132
+ else
133
+ res = default_columns
134
+ end
135
+
136
+ # merge values for each field if the record is specified
137
+ @record && res.map! do |c|
138
+ value = @record.send(normalize_column(c)[:name])
139
+ value.nil? ? c : normalize_column(c).merge(:value => value)
140
+ end
141
+
142
+ res
143
+ end
144
+
145
+ XTYPE_MAP = {
146
+ :integer => :numberfield,
147
+ :boolean => :xcheckbox,
148
+ :date => :datefield,
149
+ :datetime => :xdatetime,
150
+ :text => :textarea,
151
+ :json => :jsonfield
152
+ # :string => :textfield
153
+ }
154
+
155
+ def default_columns
156
+ # columns specified in widget's config
157
+ columns_from_config = config[:columns] && normalize_columns(config[:columns])
158
+
159
+ if columns_from_config
160
+ # reverse-merge each column hash from config with each column hash from exposed_attributes (columns from config have higher priority)
161
+ for c in columns_from_config
162
+ corresponding_exposed_column = predefined_columns.find{ |k| k[:name] == c[:name] }
163
+ c.reverse_merge!(corresponding_exposed_column) if corresponding_exposed_column
164
+ end
165
+ columns_for_create = columns_from_config
166
+ elsif predefined_columns
167
+ # we didn't have columns configured in widget's config, so, use the columns from the data class
168
+ columns_for_create = predefined_columns
169
+ else
170
+ raise ArgumentError, "No columns specified for widget '#{id_name}'"
171
+ end
172
+
173
+ columns_for_create.map! do |c|
174
+ if data_class
175
+ # Try to figure out the configuration from data class
176
+ # detect :assoc__method
177
+ if c[:name].to_s.index('__')
178
+ assoc_name, method = c[:name].to_s.split('__').map(&:to_sym)
179
+ if assoc = data_class.reflect_on_association(assoc_name)
180
+ assoc_column = assoc.klass.columns_hash[method.to_s]
181
+ assoc_method_type = assoc_column.try(:type)
182
+ if assoc_method_type
183
+ c[:xtype] ||= XTYPE_MAP[assoc_method_type] == :xcheckbox ? :xcheckbox : :combobox
184
+ end
185
+ end
186
+ end
187
+
188
+ # detect association column (e.g. :category_id)
189
+ if assoc = data_class.reflect_on_all_associations.detect{|a| a.primary_key_name.to_sym == c[:name]}
190
+ c[:xtype] ||= :combobox
191
+ assoc_method = %w{name title label id}.detect{|m| (assoc.klass.instance_methods + assoc.klass.column_names).include?(m) } || assoc.klass.primary_key
192
+ c[:name] = "#{assoc.name}__#{assoc_method}".to_sym
193
+ end
194
+ c[:hidden] = true if c[:name] == data_class.primary_key.to_sym && c[:hidden].nil? # hide ID column by default
195
+
196
+ end
197
+
198
+ # detect column type
199
+ type = c[:type] || data_class && data_class.columns_hash[c[:name].to_s].try(:type) || :string
200
+ c[:type] ||= type
201
+
202
+ c[:xtype] ||= XTYPE_MAP[type] unless XTYPE_MAP[type].nil?
203
+
204
+ # if the column is finally simply {:name => "something"}, cut it down to "something"
205
+ c.reject{ |k,v| k == :name }.empty? ? c[:name] : c
206
+ end
207
+
208
+ columns_for_create
209
+
210
+ end
211
+
212
+ include Plugins::ConfigurationTool if config[:config_tool_available] # it will load ConfigurationPanel into a modal window
213
+ end
214
+ end
@@ -0,0 +1,74 @@
1
+ module Netzke
2
+ module FormPanelApi
3
+ # API handling form submission
4
+ def submit(params)
5
+ data_hsh = ActiveSupport::JSON.decode(params[:data])
6
+ create_or_update_record(data_hsh)
7
+ end
8
+
9
+ # Creates/updates a record from hash
10
+ def create_or_update_record(hsh)
11
+ klass = config[:data_class_name].constantize
12
+ @record = klass.find_by_id(hsh.delete("id"))
13
+ success = true
14
+
15
+ @record = klass.new if @record.nil?
16
+
17
+ hsh.each_pair do |k,v|
18
+ begin
19
+ @record.send("#{k}=",v)
20
+ rescue StandardError => exc
21
+ logger.debug "!!! FormPanelApi#create_or_update_record exception: #{exc.inspect}\n"
22
+ flash :error => exc.message
23
+ success = false
24
+ break
25
+ end
26
+ end
27
+
28
+ if success && @record.save
29
+ {:set_form_values => @record.to_array(columns)}
30
+ else
31
+ # flash eventual errors
32
+ @record.errors.each_full do |msg|
33
+ flash :error => msg
34
+ end
35
+ {:feedback => @flash}
36
+ end
37
+ end
38
+
39
+ # API handling form load
40
+ # def load(params)
41
+ # klass = config[:data_class_name].constantize
42
+ # case params[:neighbour]
43
+ # when "previous" then @record = klass.previous(params[:id])
44
+ # when "next" then @record = klass.next(params[:id])
45
+ # else @record = klass.find(params[:id])
46
+ # end
47
+ # {:data => [array_of_values]}
48
+ # end
49
+
50
+ def load(params)
51
+ record = data_class && data_class.find_by_id(params[:id])
52
+ {:set_form_values => record.to_array(columns)}
53
+ end
54
+
55
+ # API that returns options for a combobox
56
+ def get_combobox_options(params)
57
+ column = params[:column]
58
+ query = params[:query]
59
+
60
+ {:data => config[:data_class_name].constantize.options_for(column, query).map{|s| [s]}}
61
+ end
62
+
63
+ def configuration_panel__fields__get_combobox_options(params)
64
+ query = params[:query]
65
+ {:data => (predefined_columns.map{ |c| c[:name].to_s }).grep(/^#{query}/).map{ |n| [n] }}.to_nifty_json
66
+ end
67
+
68
+ # Returns array of form values according to the configured columns
69
+ def array_of_values
70
+ @record && @record.to_array(columns)
71
+ end
72
+
73
+ end
74
+ end
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Ext.ux.form.XCheckbox - checkbox with configurable submit values
3
+ *
4
+ * @author Ing. Jozef Sakalos
5
+ * @version $Id: Ext.ux.form.XCheckbox.js 441 2009-01-12 11:10:10Z jozo $
6
+ * @date 10. February 2008
7
+ *
8
+ *
9
+ * @license Ext.ux.form.XCheckbox is licensed under the terms of
10
+ * the Open Source LGPL 3.0 license. Commercial use is permitted to the extent
11
+ * that the code/component(s) do NOT become part of another Open Source or Commercially
12
+ * licensed development library or toolkit without explicit permission.
13
+ *
14
+ * License details: http://www.gnu.org/licenses/lgpl.html
15
+ */
16
+
17
+ /*global Ext */
18
+
19
+ /**
20
+ * @class Ext.ux.XCheckbox
21
+ * @extends Ext.form.Checkbox
22
+ */
23
+ Ext.ns('Ext.ux.form');
24
+ Ext.ux.form.XCheckbox = Ext.extend(Ext.form.Checkbox, {
25
+ submitOffValue:'false'
26
+ ,submitOnValue:'true'
27
+
28
+ ,onRender:function() {
29
+
30
+ this.inputValue = this.submitOnValue;
31
+
32
+ // call parent
33
+ Ext.ux.form.XCheckbox.superclass.onRender.apply(this, arguments);
34
+
35
+ // create hidden field that is submitted if checkbox is not checked
36
+ this.hiddenField = this.wrap.insertFirst({tag:'input', type:'hidden'});
37
+
38
+ // support tooltip
39
+ if(this.tooltip) {
40
+ this.imageEl.set({qtip:this.tooltip});
41
+ }
42
+
43
+ // update value of hidden field
44
+ this.updateHidden();
45
+
46
+ } // eo function onRender
47
+
48
+ /**
49
+ * Calls parent and updates hiddenField
50
+ * @private
51
+ */
52
+ ,setValue:function(v) {
53
+ v = this.convertValue(v);
54
+ this.updateHidden(v);
55
+ Ext.ux.form.XCheckbox.superclass.setValue.apply(this, arguments);
56
+ } // eo function setValue
57
+
58
+ /**
59
+ * Updates hiddenField
60
+ * @private
61
+ */
62
+ ,updateHidden:function(v) {
63
+ v = undefined !== v ? v : this.checked;
64
+ v = this.convertValue(v);
65
+ if(this.hiddenField) {
66
+ this.hiddenField.dom.value = v ? this.submitOnValue : this.submitOffValue;
67
+ this.hiddenField.dom.name = v ? '' : this.el.dom.name;
68
+ }
69
+ } // eo function updateHidden
70
+
71
+ /**
72
+ * Converts value to boolean
73
+ * @private
74
+ */
75
+ ,convertValue:function(v) {
76
+ return (v === true || v === 'true' || v == 1 || v === this.submitOnValue || String(v).toLowerCase() === 'on');
77
+ } // eo function convertValue
78
+
79
+ }); // eo extend
80
+
81
+ // register xtype
82
+ Ext.reg('xcheckbox', Ext.ux.form.XCheckbox);
@@ -0,0 +1,129 @@
1
+ module Netzke
2
+ module FormPanelJs
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ def js_base_class
9
+ "Ext.FormPanel"
10
+ end
11
+
12
+ def js_extend_properties
13
+ {
14
+ :body_style => 'padding:5px 5px 0',
15
+ :auto_scroll => true,
16
+ :label_width => 150,
17
+ :default_type => 'textfield',
18
+ # :label_align => 'top',
19
+
20
+ :init_component => <<-END_OF_JAVASCRIPT.l,
21
+ function(){
22
+ var recordFields = []; // Record
23
+ this.items = [];
24
+ var index = 0;
25
+
26
+ // Process columns
27
+ Ext.each(this.clmns, function(field){
28
+ if (typeof field == 'string') field = {name:field}; // normalize field
29
+ if (!field.hidden || field.name == 'id') {
30
+ recordFields.push({name:field.name, mapping:index++});
31
+
32
+ var defaultColumnConfig = Ext.apply({}, this.defaultColumnConfig);
33
+ var columnConfig = Ext.apply(defaultColumnConfig, field);
34
+
35
+ // apply dynamically defined properties
36
+ Ext.apply(columnConfig, {
37
+ fieldLabel: columnConfig.fieldLabel || columnConfig.name.humanize(),
38
+ hideLabel: columnConfig.hidden, // completely hide fields marked "hidden"
39
+ parentId: this.id,
40
+ name: columnConfig.name,
41
+ checked: columnConfig.xtype == "xcheckbox" ? columnConfig.value : null // checkbox state
42
+ });
43
+
44
+ this.items.push(columnConfig);
45
+ }
46
+ }, this);
47
+
48
+ var Record = Ext.data.Record.create(recordFields);
49
+ this.reader = new Ext.data.RecordArrayReader({root:"data"}, Record);
50
+
51
+ delete this.clmns; // we don't need them anymore
52
+
53
+ // Now let Ext.form.FormPanel do the rest
54
+ Ext.netzke.cache.FormPanel.superclass.initComponent.call(this);
55
+
56
+ // Apply event
57
+ this.addEvents('apply');
58
+ }
59
+ END_OF_JAVASCRIPT
60
+
61
+ # Defaults for each field
62
+ :defaults => {
63
+ # :anchor => '-20', # to leave some space for the scrollbar
64
+ :width => 180,
65
+ :listeners => {
66
+ # On "return" key, submit the form
67
+ :specialkey => {
68
+ :fn => <<-END_OF_JAVASCRIPT.l
69
+ function(field, event){
70
+ if (event.getKey() == 13) this.ownerCt.apply();
71
+ }
72
+ END_OF_JAVASCRIPT
73
+ }
74
+ }
75
+ },
76
+
77
+ :default_column_config => config_columns.inject({}){ |r, c| r.merge!({
78
+ c[:name] => c[:default]
79
+ }) },
80
+
81
+ :set_form_values => <<-END_OF_JAVASCRIPT.l,
82
+ function(values){
83
+ this.form.loadRecord(this.reader.readRecords({data:[values]}).records[0]);
84
+ }
85
+ END_OF_JAVASCRIPT
86
+
87
+ :load_record => <<-END_OF_JAVASCRIPT.l,
88
+ function(id, neighbour){
89
+ this.load({id:id});
90
+ // var proxy = new Ext.data.HttpProxy({url:this.initialConfig.api.load});
91
+ // proxy.load({id:id, neighbour:neighbour}, this.reader, function(data){
92
+ // if (data){
93
+ // this.form.loadRecord(data.records[0])
94
+ // }
95
+ // }, this)
96
+ }
97
+ END_OF_JAVASCRIPT
98
+
99
+ # :previous => <<-END_OF_JAVASCRIPT.l,
100
+ # function() {
101
+ # var currentId = this.form.getValues().id;
102
+ # this.loadRecord(currentId, 'previous');
103
+ # }
104
+ # END_OF_JAVASCRIPT
105
+ #
106
+ # :next => <<-END_OF_JAVASCRIPT.l,
107
+ # function() {
108
+ # var currentId = this.form.getValues().id;
109
+ # this.loadRecord(currentId, 'next');
110
+ # }
111
+ # END_OF_JAVASCRIPT
112
+
113
+ :apply => <<-END_OF_JAVASCRIPT.l
114
+ function() {
115
+ if (this.fireEvent('apply', this)) {
116
+ var values = this.form.getValues();
117
+ for (var k in values) {
118
+ if (values[k] == "") {delete values[k]}
119
+ }
120
+ this.submit(Ext.apply((this.baseParams || {}), {data:Ext.encode(values)}));
121
+ }
122
+ }
123
+ END_OF_JAVASCRIPT
124
+ }
125
+ end
126
+
127
+ end
128
+ end
129
+ end