netzke-basepack 0.1.2

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 (66) hide show
  1. data/CHANGELOG +18 -0
  2. data/LICENSE +20 -0
  3. data/Manifest +64 -0
  4. data/README.mdown +74 -0
  5. data/Rakefile +14 -0
  6. data/generators/netzke_basepack/USAGE +8 -0
  7. data/generators/netzke_basepack/netzke_basepack_generator.rb +8 -0
  8. data/generators/netzke_basepack/netzke_grid_generator.rb +7 -0
  9. data/generators/netzke_basepack/templates/create_netzke_grid_columns.rb +21 -0
  10. data/init.rb +1 -0
  11. data/install.rb +1 -0
  12. data/javascripts/basepack.js +52 -0
  13. data/javascripts/filters.js +7 -0
  14. data/lib/app/models/netzke_grid_column.rb +23 -0
  15. data/lib/netzke-basepack.rb +28 -0
  16. data/lib/netzke/accordion.rb +11 -0
  17. data/lib/netzke/ar_ext.rb +163 -0
  18. data/lib/netzke/column.rb +43 -0
  19. data/lib/netzke/container.rb +81 -0
  20. data/lib/netzke/grid.rb +120 -0
  21. data/lib/netzke/grid_interface.rb +156 -0
  22. data/lib/netzke/grid_js_builder.rb +276 -0
  23. data/lib/netzke/preference_grid.rb +43 -0
  24. data/lib/netzke/properties_tool.rb +66 -0
  25. data/lib/netzke/property_grid.rb +60 -0
  26. data/netzke-basepack.gemspec +38 -0
  27. data/tasks/netzke_basepack_tasks.rake +4 -0
  28. data/test/app_root/app/controllers/application.rb +2 -0
  29. data/test/app_root/app/models/book.rb +9 -0
  30. data/test/app_root/app/models/category.rb +2 -0
  31. data/test/app_root/app/models/city.rb +3 -0
  32. data/test/app_root/app/models/continent.rb +2 -0
  33. data/test/app_root/app/models/country.rb +3 -0
  34. data/test/app_root/app/models/genre.rb +3 -0
  35. data/test/app_root/config/boot.rb +114 -0
  36. data/test/app_root/config/database.yml +21 -0
  37. data/test/app_root/config/environment.rb +13 -0
  38. data/test/app_root/config/environments/in_memory.rb +0 -0
  39. data/test/app_root/config/environments/mysql.rb +0 -0
  40. data/test/app_root/config/environments/postgresql.rb +0 -0
  41. data/test/app_root/config/environments/sqlite.rb +0 -0
  42. data/test/app_root/config/environments/sqlite3.rb +0 -0
  43. data/test/app_root/config/routes.rb +4 -0
  44. data/test/app_root/db/migrate/20081222033343_create_books.rb +15 -0
  45. data/test/app_root/db/migrate/20081222033440_create_genres.rb +14 -0
  46. data/test/app_root/db/migrate/20081222035855_create_netzke_preferences.rb +18 -0
  47. data/test/app_root/db/migrate/20081223024935_create_categories.rb +13 -0
  48. data/test/app_root/db/migrate/20081223025635_create_countries.rb +14 -0
  49. data/test/app_root/db/migrate/20081223025653_create_continents.rb +13 -0
  50. data/test/app_root/db/migrate/20081223025732_create_cities.rb +15 -0
  51. data/test/app_root/script/console +7 -0
  52. data/test/ar_ext_test.rb +39 -0
  53. data/test/column_test.rb +27 -0
  54. data/test/console_with_fixtures.rb +4 -0
  55. data/test/fixtures/books.yml +9 -0
  56. data/test/fixtures/categories.yml +7 -0
  57. data/test/fixtures/cities.yml +21 -0
  58. data/test/fixtures/continents.yml +7 -0
  59. data/test/fixtures/countries.yml +9 -0
  60. data/test/fixtures/genres.yml +9 -0
  61. data/test/grid_test.rb +43 -0
  62. data/test/netzke_basepack_test.rb +8 -0
  63. data/test/schema.rb +10 -0
  64. data/test/test_helper.rb +20 -0
  65. data/uninstall.rb +1 -0
  66. metadata +162 -0
@@ -0,0 +1,43 @@
1
+ module Netzke
2
+ class Column
3
+ def self.default_columns_for_widget(widget)
4
+ raise ArgumentError, "No data_class_name specified for widget #{widget.config[:name]}" if widget.config[:data_class_name].nil?
5
+
6
+ # layout = NetzkeLayout.create(:widget_name => widget.id_name, :items_class => self.name, :user_id => NetzkeLayout.user_id)
7
+
8
+ data_class = widget.config[:data_class_name].constantize
9
+
10
+ exposed_columns = normalize_columns(data_class.exposed_columns) # columns exposed from the data class
11
+
12
+ columns_from_config = widget.config[:columns] && normalize_columns(widget.config[:columns]) # columns specified in widget's config
13
+
14
+ if columns_from_config
15
+ # reverse-merge each column hash from config with each column hash from exposed_columns (columns from config have higher priority)
16
+ for c in columns_from_config
17
+ corresponding_exposed_column = exposed_columns.find{ |k| k[:name] == c[:name] }
18
+ c.reverse_merge!(corresponding_exposed_column) if corresponding_exposed_column
19
+ end
20
+ columns_for_create = columns_from_config
21
+ else
22
+ # we didn't have columns configured in widget's config, so, use the columns from the data class
23
+ columns_for_create = exposed_columns
24
+ end
25
+
26
+ res = []
27
+ for c in columns_for_create
28
+ # finally reverse-merge them with the defaults from the data_class
29
+ res << data_class.default_column_config(c)
30
+ end
31
+
32
+ res
33
+ end
34
+
35
+ protected
36
+
37
+ # like this: [:col1, {:name => :col2}, :col3] => [{:name => :col1}, {:name => :col2}, {:name => :col3}]
38
+ def self.normalize_columns(items)
39
+ items.map{|c| c.is_a?(Symbol) ? {:name => c} : c}
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,81 @@
1
+ module Netzke
2
+ #
3
+ # Base class for Accordion and TabPanel widgets, it shouldn't be used as a stand-alone class.
4
+ #
5
+ class Container < Base
6
+ def initialize(*args)
7
+ super
8
+ for item in initial_items do
9
+ add_aggregatee item
10
+ items << item.keys.first
11
+ end
12
+ end
13
+
14
+ def initial_dependencies
15
+ dep = super
16
+ for item in items
17
+ candidate_dependency = aggregatees[item][:widget_class_name]
18
+ dep << candidate_dependency unless dep.include?(candidate_dependency)
19
+ end
20
+ dep
21
+ end
22
+
23
+ def js_before_constructor
24
+ js_widget_items
25
+ end
26
+
27
+ def items
28
+ @items ||= []
29
+ end
30
+
31
+ def initial_items
32
+ config[:items] || []
33
+ end
34
+
35
+ def js_widget_items
36
+ res = ""
37
+ item_aggregatees.each_pair do |k,v|
38
+ next if v[:late_aggregation]
39
+ res << <<-JS
40
+ var #{k.to_js} = new Ext.componentCache['#{v[:widget_class_name]}'](config.#{k.to_js}Config);
41
+ JS
42
+ end
43
+ res
44
+ end
45
+
46
+ def js_items
47
+ items.inject([]) do |a,i|
48
+ a << {
49
+ :title => i.to_s.humanize,
50
+ :layout => 'fit',
51
+ :id => i.to_s,
52
+ # :id => "#{config[:name]}_#{i.to_s}",
53
+ :items => ([i.to_s.to_js.l] if !aggregatees[i][:late_aggregation]),
54
+ # these listeners will be different for tab_panel and accordion
55
+ :collapsed => !aggregatees[i][:active],
56
+ :listeners => {
57
+ # :activate => {:fn => "function(p){this.feedback(p.id)}".l, :scope => this},
58
+ :expand => {:fn => "this.loadItemWidget".l, :scope => this}
59
+ }
60
+ }
61
+ end
62
+ end
63
+
64
+ def js_extend_properties
65
+ {
66
+ # loads widget into the panel if it's not loaded yet
67
+ :load_item_widget => <<-JS.l,
68
+ function(panel) {
69
+ if (!panel.getWidget()) panel.loadWidget(this.id + "__" + panel.id + "__get_widget");
70
+ // if (!this.getWidgetByPanel(panel)) this.loadWidget(panel, this.initialConfig[panel.id+'Config'].interface.getWidget);
71
+ }
72
+ JS
73
+ }
74
+ end
75
+
76
+ protected
77
+ def item_aggregatees
78
+ aggregatees.delete_if{|k,v| !@items.include?(k)}
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,120 @@
1
+ require 'searchlogic'
2
+ module Netzke
3
+ #
4
+ # Functionality:
5
+ # * data operations - get, post, delete, create
6
+ # * column resize and move
7
+ # * permissions
8
+ # * sorting
9
+ # * pagination
10
+ # * properties and column configuration
11
+ #
12
+ class Grid < Base
13
+ include GridJsBuilder
14
+ include GridInterface
15
+
16
+ # define connection points between client side and server side of Grid. See implementation of equally named methods in the GridInterface module.
17
+ interface :get_data, :post_data, :delete_data, :resize_column, :move_column, :get_cb_choices
18
+
19
+ def initial_config
20
+ {
21
+ :ext_config => {:properties => true, :column_filters => true},
22
+ :layout_manager => "NetzkeLayout",
23
+ :column_resize => true,
24
+ :column_move => true
25
+ }
26
+ end
27
+
28
+ def property_widgets
29
+ [{
30
+ :columns => {
31
+ :widget_class_name => "Grid",
32
+ :data_class_name => column_manager_class_name,
33
+ :ext_config => {:title => false, :properties => false},
34
+ :active => true
35
+ }
36
+ },{
37
+ :general => {
38
+ :widget_class_name => "PreferenceGrid",
39
+ :host_widget_name => @id_name,
40
+ :default_properties => available_permissions.map{ |k| {:name => "permissions.#{k}", :value => @permissions[k.to_sym]}},
41
+ :ext_config => {:title => false}
42
+ }
43
+ }]
44
+ end
45
+
46
+ ## Data for properties grid
47
+ def properties__columns__get_data(params = {})
48
+ columns_widget = aggregatee_instance(:properties__columns)
49
+
50
+ layout_id = layout_manager_class.by_widget(id_name).id
51
+ columns_widget.interface_get_data(params.merge(:filters => {:layout_id => layout_id}))
52
+ end
53
+
54
+ def properties__general__load_source(params = {})
55
+ w = aggregatee_instance(:properties__general)
56
+ w.interface_load_source(params)
57
+ end
58
+
59
+
60
+
61
+ protected
62
+
63
+ def layout_manager_class
64
+ config[:layout_manager] && config[:layout_manager].constantize
65
+ end
66
+
67
+ def column_manager_class_name
68
+ "NetzkeGridColumn"
69
+ end
70
+
71
+ def column_manager_class
72
+ column_manager_class_name.constantize
73
+ rescue NameError
74
+ nil
75
+ end
76
+
77
+ def available_permissions
78
+ %w(read update create delete)
79
+ end
80
+
81
+ public
82
+
83
+ # get columns from layout manager
84
+ def get_columns
85
+ if layout_manager_class
86
+ layout = layout_manager_class.by_widget(id_name)
87
+ layout ||= column_manager_class.create_layout_for_widget(self)
88
+ layout.items_hash # TODO: bad name!
89
+ else
90
+ Netzke::Column.default_columns_for_widget(self)
91
+ end
92
+ end
93
+
94
+ def tools
95
+ [{:id => 'refresh', :on => {:click => 'refreshClick'}}]
96
+ end
97
+
98
+ def actions
99
+ [{
100
+ :text => 'Add', :handler => 'add', :disabled => @pref['permissions.create'] == false
101
+ },{
102
+ :text => 'Edit', :handler => 'edit', :disabled => @pref['permissions.update'] == false
103
+ },{
104
+ :text => 'Delete', :handler => 'delete', :disabled => @pref['permissions.delete'] == false
105
+ },{
106
+ :text => 'Apply', :handler => 'submit', :disabled => @pref['permissions.update'] == false && @pref['permissions.create'] == false
107
+ }]
108
+ end
109
+
110
+
111
+
112
+ # Uncomment to enable a menu duplicating the actions
113
+ # def js_menus
114
+ # [{:text => "config.dataClassName".l, :menu => "config.actions".l}]
115
+ # end
116
+
117
+ # include ColumnOperations
118
+ include PropertiesTool # it will load aggregation with name :properties into a modal window
119
+ end
120
+ end
@@ -0,0 +1,156 @@
1
+ module Netzke::GridInterface
2
+ def post_data(params)
3
+ [:create, :update].each do |operation|
4
+ data = JSON.parse(params.delete("#{operation}d_records".to_sym)) if params["#{operation}d_records".to_sym]
5
+ process_data(data, operation) if !data.nil?
6
+ end
7
+ {:success => true, :flash => @flash}
8
+ end
9
+
10
+ def get_data(params = {})
11
+ if @permissions[:read]
12
+ records = get_records(params)
13
+ {:data => records, :total => records.total_records}
14
+ else
15
+ flash :error => "You don't have permissions to read data"
16
+ {:success => false, :flash => @flash}
17
+ end
18
+ end
19
+
20
+ def delete_data(params = {})
21
+ if @permissions[:delete]
22
+ record_ids = JSON.parse(params.delete(:records))
23
+ klass = config[:data_class_name].constantize
24
+ klass.delete(record_ids)
25
+ flash :notice => "Deleted #{record_ids.size} record(s)"
26
+ success = true
27
+ else
28
+ flash :error => "You don't have permissions to delete data"
29
+ success = false
30
+ end
31
+ {:success => success, :flash => @flash}
32
+ end
33
+
34
+ def resize_column(params)
35
+ raise "Called interface_resize_column while not configured to do so" unless config[:column_resize]
36
+ l_item = layout_manager_class.by_widget(id_name).layout_items[params[:index].to_i]
37
+ l_item.width = params[:size]
38
+ l_item.save!
39
+ {}
40
+ end
41
+
42
+ def move_column(params)
43
+ raise "Called interface_move_column while not configured to do so" unless config[:column_move]
44
+ layout_manager_class.by_widget(id_name).move_item(params[:old_index].to_i, params[:new_index].to_i)
45
+ {}
46
+ end
47
+
48
+ # Return the choices for the column
49
+ def get_cb_choices(params)
50
+ column = params[:column]
51
+ query = params[:query]
52
+
53
+ {:data => config[:data_class_name].constantize.choices_for(column, query).map{|s| [s]}}
54
+ end
55
+
56
+
57
+ protected
58
+
59
+ # operation => :update || :create
60
+ def process_data(data, operation)
61
+ if @permissions[operation]
62
+ klass = config[:data_class_name].constantize
63
+ modified_records = 0
64
+ data.each do |record_hash|
65
+ record = operation == :create ? klass.create : klass.find(record_hash.delete('id'))
66
+ logger.debug { "!!! record: #{record.inspect}" }
67
+ success = true
68
+ exception = nil
69
+
70
+ # process all attirubutes for the same record (OPTIMIZE: we can use update_attributes separately for regular attributes to speed things up)
71
+ record_hash.each_pair do |k,v|
72
+ begin
73
+ record.send("#{k}=",v)
74
+ rescue ArgumentError => exc
75
+ flash :error => exc.message
76
+ success = false
77
+ break
78
+ end
79
+ end
80
+
81
+ # try to save
82
+ modified_records += 1 if success && record.save
83
+
84
+ # flash eventual errors
85
+ record.errors.each_full do |msg|
86
+ flash :error => msg
87
+ end
88
+
89
+ flash :notice => "#{operation.to_s.capitalize}d #{modified_records} records"
90
+ end
91
+ else
92
+ flash :error => "You don't have permissions to #{operation} data"
93
+ end
94
+ end
95
+
96
+ # get records
97
+ def get_records(params)
98
+ search_params = normalize_params(params)
99
+ raise ArgumentError, "No data_class_name specified for widget '#{config[:name]}'" if !config[:data_class_name]
100
+ records = config[:data_class_name].constantize.all(search_params.clone) # clone needed as searchlogic removes :conditions key from the hash
101
+ output_array = []
102
+ records.each do |r|
103
+ r_array = []
104
+ self.get_columns.each do |column|
105
+ r_array << r.send(column[:name])
106
+ end
107
+ output_array << r_array
108
+ end
109
+
110
+ # add total_entries accessor to the result
111
+ class << output_array
112
+ attr :total_records, true
113
+ end
114
+ total_records_count = config[:data_class_name].constantize.count(search_params)
115
+ output_array.total_records = total_records_count
116
+
117
+ output_array
118
+ end
119
+
120
+ def convert_filters(column_filter)
121
+ res = {}
122
+ column_filter.each_pair do |k,v|
123
+ field = v["field"]
124
+ case v["data"]["type"]
125
+ when "string"
126
+ field << "_contains"
127
+ when "numeric"
128
+ field << "_#{v["data"]["comparison"]}"
129
+ end
130
+ value = v["data"]["value"]
131
+ res.merge!({field => value})
132
+ end
133
+ res
134
+ end
135
+
136
+ # make params understandable to searchlogic
137
+ def normalize_params(params)
138
+ # filters
139
+ conditions = params[:filter] && convert_filters(params[:filter])
140
+
141
+ normalized_conditions = {}
142
+ conditions && conditions.each_pair do |k, v|
143
+ assoc, method = k.split('__')
144
+ normalized_conditions.merge!(method.nil? ? {assoc => v} : {assoc => {method => v}})
145
+ end
146
+
147
+ # sorting
148
+ order_by = if params[:sort]
149
+ assoc, method = params[:sort].split('__')
150
+ method.nil? ? assoc : {assoc => method}
151
+ end
152
+
153
+ page = params[:start].to_i/params[:limit].to_i + 1 if params[:limit]
154
+ {:per_page => params[:limit], :page => page, :order_by => order_by, :order_as => params[:dir], :conditions => normalized_conditions}
155
+ end
156
+ end
@@ -0,0 +1,276 @@
1
+ module Netzke::GridJsBuilder
2
+ def js_base_class
3
+ 'Ext.grid.EditorGridPanel'
4
+ end
5
+
6
+ def js_bbar
7
+ <<-JS.l
8
+ (config.rowsPerPage) ? new Ext.PagingToolbar({
9
+ pageSize:config.rowsPerPage,
10
+ items:config.actions,
11
+ store:ds,
12
+ emptyMsg:'Empty'}) : config.actions
13
+ JS
14
+ end
15
+
16
+ def js_default_config
17
+ super.merge({
18
+ :store => "ds".l,
19
+ :cm => "cm".l,
20
+ :sel_model => "new Ext.grid.RowSelectionModel()".l,
21
+ :auto_scroll => true,
22
+ :click_to_edit => 2,
23
+ :track_mouse_over => true,
24
+ # :bbar => "config.actions".l,
25
+ :bbar => js_bbar,
26
+ :plugins => "plugins".l,
27
+
28
+ #custom configs
29
+ :auto_load_data => true
30
+ })
31
+ end
32
+
33
+ def js_before_constructor
34
+ <<-JS
35
+ var plugins = [];
36
+ if (!config.columns) this.feedback('No columns defined for grid '+config.id);
37
+ this.recordConfig = [];
38
+ Ext.each(config.columns, function(column){this.recordConfig.push({name:column.name})}, this);
39
+ this.Row = Ext.data.Record.create(this.recordConfig);
40
+
41
+ var ds = new Ext.data.Store({
42
+ proxy: this.proxy = new Ext.data.HttpProxy({url:config.interface.getData}),
43
+ reader: new Ext.data.ArrayReader({root: "data", totalProperty: "total", successProperty: "succes", id:0}, this.Row),
44
+ remoteSort: true,
45
+ listeners:{'loadexception':{
46
+ fn:this.loadExceptionHandler,
47
+ scope:this
48
+ }}
49
+ });
50
+
51
+ this.cmConfig = [];
52
+ Ext.each(config.columns, function(c){
53
+ var editor = c.readOnly ? null : Ext.netzke.editors[c.showsAs](c, config);
54
+
55
+ this.cmConfig.push({
56
+ header: c.label || c.name,
57
+ dataIndex: c.name,
58
+ hidden: c.hidden,
59
+ width: c.width,
60
+ editor: editor,
61
+ sortable: true
62
+ })
63
+ }, this);
64
+
65
+ var cm = new Ext.grid.ColumnModel(this.cmConfig);
66
+
67
+ this.addEvents("refresh");
68
+
69
+ // Filters
70
+ if (config.columnFilters) {
71
+ var filters = []
72
+ Ext.each(config.columns, function(c){
73
+ filters.push({type:Ext.netzke.filterMap[c.showsAs], dataIndex:c.name})
74
+ })
75
+ var gridFilters = new Ext.grid.GridFilters({filters:filters});
76
+ plugins.push(gridFilters);
77
+ }
78
+
79
+ JS
80
+ end
81
+
82
+ def js_config
83
+ res = super
84
+ # we pass column config at the time of instantiating the JS class
85
+ res.merge!(:columns => get_columns || config[:columns]) # first try to get columns from DB, then from config
86
+ res.merge!(:data_class_name => config[:data_class_name])
87
+ res
88
+ end
89
+
90
+ def js_listeners
91
+ super.merge({
92
+ :columnresize => (config[:column_resize] ? {:fn => "this.onColumnResize".l, :scope => this} : nil),
93
+ :columnmove => (config[:column_move] ? {:fn => "this.onColumnMove".l, :scope => this} : nil)
94
+ })
95
+ end
96
+
97
+
98
+ def js_extend_properties
99
+ {
100
+ :on_widget_load => <<-JS.l,
101
+ function(){
102
+ // auto-load
103
+ if (this.initialConfig.autoLoadData) {
104
+ // if we have a paging toolbar, load the first page, otherwise
105
+ if (this.getBottomToolbar().changePage) this.getBottomToolbar().changePage(0); else this.store.load();
106
+ }
107
+ }
108
+ JS
109
+
110
+ :load_exception_handler => <<-JS.l,
111
+ function(proxy, options, response, error){
112
+ if (response.status == 200 && (responseObject = Ext.decode(response.responseText)) && responseObject.flash){
113
+ this.feedback(responseObject.flash)
114
+ } else {
115
+ if (error){
116
+ this.feedback(error.message);
117
+ } else {
118
+ this.feedback(response.statusText)
119
+ }
120
+ }
121
+ }
122
+ JS
123
+
124
+ :add => <<-JS.l,
125
+ function(){
126
+ var rowConfig = {};
127
+ Ext.each(this.initialConfig.columns, function(c){
128
+ rowConfig[c.name] = c.defaultValue || ''; // FIXME: if the user is happy with all the defaults, the record won't be 'dirty'
129
+ }, this);
130
+
131
+ var r = new this.Row(rowConfig); // TODO: add default values
132
+ r.set('id', -r.id); // to distinguish new records by negative values
133
+ this.stopEditing();
134
+ this.store.add(r);
135
+ this.store.newRecords = this.store.newRecords || []
136
+ this.store.newRecords.push(r);
137
+ // console.info(this.store.newRecords);
138
+ this.tryStartEditing(this.store.indexOf(r));
139
+ }
140
+ JS
141
+
142
+ :edit => <<-JS.l,
143
+ function(){
144
+ var row = this.getSelectionModel().getSelected();
145
+ if (row){
146
+ this.tryStartEditing(this.store.indexOf(row))
147
+ }
148
+ }
149
+ JS
150
+
151
+ # try editing the first editable (not hidden, not read-only) sell
152
+ :try_start_editing => <<-JS.l,
153
+ function(row){
154
+ if (row == null) return;
155
+ var editableColumns = this.getColumnModel().getColumnsBy(function(columnConfig, index){
156
+ return !columnConfig.hidden && !!columnConfig.editor;
157
+ });
158
+ // console.info(editableColumns);
159
+ var firstEditableColumn = editableColumns[0];
160
+ if (firstEditableColumn){
161
+ this.startEditing(row, firstEditableColumn.id);
162
+ }
163
+ }
164
+ JS
165
+
166
+ :delete => <<-JS.l,
167
+ function() {
168
+ if (this.getSelectionModel().hasSelection()){
169
+ Ext.Msg.confirm('Confirm', 'Are you sure?', function(btn){
170
+ if (btn == 'yes') {
171
+ var records = []
172
+ this.getSelectionModel().each(function(r){
173
+ records.push(r.get('id'));
174
+ }, this);
175
+ Ext.Ajax.request({
176
+ url: this.initialConfig.interface.deleteData,
177
+ params: {records: Ext.encode(records)},
178
+ success:function(r){
179
+ var m = Ext.decode(r.responseText);
180
+ this.store.reload();
181
+ // this.loadWithFeedback();
182
+ this.feedback(m.flash);
183
+ },
184
+ scope:this
185
+ });
186
+ }
187
+ }, this);
188
+ }
189
+ }
190
+ JS
191
+ :submit => <<-JS.l,
192
+ function(){
193
+
194
+ var newRecords = [];
195
+ if (this.store.newRecords){
196
+ Ext.each(this.store.newRecords, function(r){
197
+ newRecords.push(r.getChanges())
198
+ r.commit() // commit the changes, so that they are not picked up by getModifiedRecords() further down
199
+ }, this);
200
+ delete this.store.newRecords;
201
+ }
202
+
203
+ var updatedRecords = [];
204
+ Ext.each(this.store.getModifiedRecords(),
205
+ function(record) {
206
+ var completeRecordData = {};
207
+ Ext.apply(completeRecordData, Ext.apply(record.getChanges(), {id:record.get('id')}));
208
+ updatedRecords.push(completeRecordData);
209
+ },
210
+ this);
211
+
212
+ if (newRecords.length > 0 || updatedRecords.length > 0) {
213
+ Ext.Ajax.request({
214
+ url:this.initialConfig.interface.postData,
215
+ params: {
216
+ updated_records: Ext.encode(updatedRecords),
217
+ created_records: Ext.encode(newRecords),
218
+ filters: this.store.baseParams.filters
219
+ },
220
+ success:function(response){
221
+ var m = Ext.decode(response.responseText);
222
+ if (m.success) {
223
+ this.store.reload();
224
+ // this.loadWithFeedback();
225
+ this.store.commitChanges();
226
+ this.feedback(m.flash);
227
+ } else {
228
+ this.feedback(m.flash);
229
+ }
230
+ },
231
+ failure:function(response){
232
+ this.feedback('Bad response from server');
233
+ },
234
+ scope:this
235
+ });
236
+ }
237
+
238
+ }
239
+ JS
240
+
241
+ :refresh_click => <<-JS.l,
242
+ function() {
243
+ // console.info(this);
244
+ // if (this.fireEvent('refresh', this) !== false) this.loadWithFeedback();
245
+ if (this.fireEvent('refresh', this) !== false) this.store.reload();
246
+ }
247
+ JS
248
+
249
+ :on_column_resize => <<-JS.l,
250
+ function(index, size){
251
+ // var column = this.getColumnModel().getDataIndex(index);
252
+ Ext.Ajax.request({
253
+ url:this.initialConfig.interface.resizeColumn,
254
+ params:{
255
+ index:index,
256
+ size:size
257
+ }
258
+ })
259
+ }
260
+ JS
261
+
262
+ :on_column_move => <<-JS.l
263
+ function(oldIndex, newIndex){
264
+ Ext.Ajax.request({
265
+ url:this.initialConfig.interface.moveColumn,
266
+ params:{
267
+ old_index:oldIndex,
268
+ new_index:newIndex
269
+ }
270
+ })
271
+ }
272
+ JS
273
+
274
+ }
275
+ end
276
+ end