skozlov-netzke-basepack 0.1.1.2 → 0.5.0

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 (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