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
@@ -1,206 +0,0 @@
1
- module Netzke
2
- module GridPanelExtras
3
- module Interface
4
- def post_data(params)
5
- success = true
6
- mod_record_ids = {}
7
- [:create, :update].each do |operation|
8
- data = ActiveSupport::JSON.decode(params.delete("#{operation}d_records".to_sym)) if params["#{operation}d_records".to_sym]
9
- if !data.nil? && !data.empty? # data may be nil for one of the operations
10
- mod_record_ids[operation] = process_data(data, operation)
11
- end
12
- break if !success
13
- end
14
- {:success => success, :flash => @flash, :mod_record_ids => mod_record_ids}
15
- end
16
-
17
- def get_data(params = {})
18
- if @permissions[:read]
19
- records = get_records(params)
20
- {:data => records, :total => records.total_records}
21
- else
22
- flash :error => "You don't have permissions to read data"
23
- {:success => false, :flash => @flash}
24
- end
25
- end
26
-
27
- def delete_data(params = {})
28
- if @permissions[:delete]
29
- record_ids = ActiveSupport::JSON.decode(params.delete(:records))
30
- klass = config[:data_class_name].constantize
31
- klass.delete(record_ids)
32
- flash :notice => "Deleted #{record_ids.size} record(s)"
33
- success = true
34
- else
35
- flash :error => "You don't have permissions to delete data"
36
- success = false
37
- end
38
- {:success => success, :flash => @flash}
39
- end
40
-
41
- def resize_column(params)
42
- raise "Called interface_resize_column while not configured to do so" unless config[:ext_config][:enable_column_resize]
43
- if config[:persistent_layout]
44
- NetzkeLayoutItem.widget = id_name
45
- column = NetzkeLayoutItem.find(params[:index].to_i + 1)
46
- column.width = params[:size].to_i
47
- column.save
48
- end
49
- {}
50
- end
51
-
52
- def hide_column(params)
53
- raise "Called interface_hide_column while not configured to do so" unless config[:ext_config][:enable_column_hide]
54
- if config[:persistent_layout]
55
- NetzkeLayoutItem.widget = id_name
56
- column = NetzkeLayoutItem.find(params[:index].to_i + 1)
57
- column.hidden = params[:hidden].to_b
58
- column.save
59
- end
60
- {}
61
- end
62
-
63
- def move_column(params)
64
- raise "Called interface_move_column while not configured to do so" unless config[:ext_config][:enable_column_move]
65
- if config[:persistent_layout]
66
- NetzkeLayoutItem.widget = id_name
67
- NetzkeLayoutItem.move_item(params[:old_index].to_i, params[:new_index].to_i)
68
- end
69
-
70
- # provide the client side with the new columns order
71
- {:columns => columns.map(&:name)}
72
- end
73
-
74
- # Return the choices for the column
75
- def get_cb_choices(params)
76
- column = params[:column]
77
- query = params[:query]
78
-
79
- {:data => config[:data_class_name].constantize.choices_for(column, query).map{|s| [s]}}
80
- end
81
-
82
-
83
- protected
84
-
85
- # operation => :update || :create
86
- def process_data(data, operation)
87
- success = true
88
- mod_record_ids = []
89
- if @permissions[operation]
90
- klass = config[:data_class_name].constantize
91
- modified_records = 0
92
- data.each do |record_hash|
93
- id = record_hash.delete('id')
94
- record = operation == :create ? klass.new : klass.find(id)
95
- success = true
96
-
97
- # process all attirubutes for the same record (OPTIMIZE: we can use update_attributes separately for regular attributes to speed things up)
98
- record_hash.each_pair do |k,v|
99
- begin
100
- record.send("#{k}=",v)
101
- rescue ArgumentError => exc
102
- flash :error => exc.message
103
- success = false
104
- break
105
- end
106
- end
107
-
108
- # try to save
109
- # modified_records += 1 if success && record.save
110
- mod_record_ids << id if success && record.save
111
-
112
- # flash eventual errors
113
- if !record.errors.empty?
114
- success = false
115
- record.errors.each_full do |msg|
116
- flash :error => msg
117
- end
118
- end
119
- end
120
- # flash :notice => "#{operation.to_s.capitalize}d #{modified_records} record(s)"
121
- else
122
- success = false
123
- flash :error => "You don't have permissions to #{operation} data"
124
- end
125
- mod_record_ids
126
- end
127
-
128
- # get records
129
- def get_records(params)
130
- search_params = normalize_params(params) # make params coming from the browser understandable by searchlogic
131
- search_params[:conditions].recursive_merge!(config[:conditions] || {}) # merge with conditions coming from the config
132
-
133
- raise ArgumentError, "No data_class_name specified for widget '#{config[:name]}'" if !config[:data_class_name]
134
- records = config[:data_class_name].constantize.all(search_params.clone) # clone needed as searchlogic removes :conditions key from the hash
135
- output_array = records.map{|r| r.to_array(columns)}
136
-
137
- # add total_entries accessor to the result
138
- class << output_array
139
- attr :total_records, true
140
- end
141
- total_records_count = config[:data_class_name].constantize.count(search_params)
142
- output_array.total_records = total_records_count
143
-
144
- output_array
145
- end
146
-
147
- #
148
- # Converts Ext.grid.GridFilters filters to searchlogic conditions, e.g.
149
- # {"0" => {
150
- # "data" => {
151
- # "type" => "numeric",
152
- # "comparison" => "gt",
153
- # "value" => 10 },
154
- # "field" => "id"
155
- # },
156
- # "1" => {
157
- # "data" => {
158
- # "type" => "string",
159
- # "value" => "pizza"
160
- # },
161
- # "field" => "food_name"
162
- # }}
163
- #
164
- # =>
165
- #
166
- # {"id_gt" => 100, "food_name_contains" => "pizza"}
167
- #
168
- def convert_filters(column_filter)
169
- res = {}
170
- column_filter.each_pair do |k,v|
171
- field = v["field"]
172
- case v["data"]["type"]
173
- when "string"
174
- field << "_contains"
175
- when "numeric"
176
- field << "_#{v["data"]["comparison"]}"
177
- end
178
- value = v["data"]["value"]
179
- res.merge!({field => value})
180
- end
181
- res
182
- end
183
-
184
- # make params understandable to searchlogic
185
- def normalize_params(params)
186
- # filters
187
- conditions = params[:filter] && convert_filters(params[:filter])
188
-
189
- normalized_conditions = {}
190
- conditions && conditions.each_pair do |k, v|
191
- assoc, method = k.split('__')
192
- normalized_conditions.merge!(method.nil? ? {assoc => v} : {assoc => {method => v}})
193
- end
194
-
195
- # sorting
196
- order_by = if params[:sort]
197
- assoc, method = params[:sort].split('__')
198
- method.nil? ? assoc : {assoc => method}
199
- end
200
-
201
- page = params[:start].to_i/params[:limit].to_i + 1 if params[:limit]
202
- {:per_page => params[:limit], :page => page, :order_by => order_by, :order_as => params[:dir], :conditions => normalized_conditions}
203
- end
204
- end
205
- end
206
- end
@@ -1,352 +0,0 @@
1
- module Netzke
2
- module GridPanelExtras
3
- module JsBuilder
4
- def self.included(base)
5
- base.extend ClassMethods
6
- end
7
-
8
- def js_config
9
- res = super
10
- res.merge!(:columns => columns)
11
- res.merge!(:data_class_name => config[:data_class_name])
12
- res
13
- end
14
-
15
- def js_ext_config
16
- super.merge({
17
- :rows_per_page => persistent_config["rows_per_page"] ||= config[:ext_config][:rows_per_page]
18
- })
19
- end
20
-
21
- module ClassMethods
22
-
23
- def js_base_class
24
- 'Ext.grid.EditorGridPanel'
25
- end
26
-
27
- def js_default_config
28
- super.merge({
29
- :store => "ds".l,
30
- :cm => "cm".l,
31
- :sel_model => "new Ext.grid.RowSelectionModel()".l,
32
- :auto_scroll => true,
33
- :click_to_edit => 2,
34
- :track_mouse_over => true,
35
- :plugins => "plugins".l,
36
- :load_mask => true,
37
-
38
- #custom configs
39
- :auto_load_data => true
40
- })
41
- end
42
-
43
- def js_before_constructor
44
- <<-JS
45
- var plugins = [];
46
- if (!config.columns) {this.feedback('No columns defined for grid '+config.id);}
47
- this.recordConfig = [];
48
- Ext.each(config.columns, function(column){this.recordConfig.push({name:column.name});}, this);
49
- this.Row = Ext.data.Record.create(this.recordConfig);
50
-
51
- var ds = new Ext.data.Store({
52
- proxy: this.proxy = new Ext.data.HttpProxy({url:config.interface.getData}),
53
- reader: new Ext.data.ArrayReader({root: "data", totalProperty: "total", successProperty: "succes", id:0}, this.Row),
54
- remoteSort: true,
55
- listeners:{'loadexception':{
56
- fn:this.loadExceptionHandler,
57
- scope:this
58
- }}
59
- });
60
-
61
- this.cmConfig = [];
62
- Ext.each(config.columns, function(c){
63
- var extConfig;
64
- try{
65
- extConfig = Ext.decode(c.extConfig);
66
- }
67
- catch(err){
68
- extConfig = {};
69
- }
70
- delete(c.extConfig);
71
-
72
- if (c.editor == 'checkbox') {
73
- var plugin = new Ext.grid.CheckColumn(Ext.apply({
74
- header : c.label || c.name,
75
- dataIndex : c.name,
76
- disabled : c.readOnly,
77
- hidden : c.hidden,
78
- width : c.width
79
- }, extConfig));
80
-
81
- plugins.push(plugin);
82
- this.cmConfig.push(plugin);
83
-
84
- } else {
85
- // editor is created by xtype stored in c.editor
86
- var editor = (c.readOnly || !config.permissions.update) ? null : Ext.ComponentMgr.create({
87
- xtype:c.editor,
88
- parentConfig:config,
89
- fieldConfig:c,
90
- selectOnFocus:true
91
- });
92
-
93
- var renderer = Ext.netzke.renderer(c.renderer);
94
-
95
- this.cmConfig.push(Ext.apply({
96
- header : c.label || c.name,
97
- dataIndex : c.name,
98
- hidden : c.hidden,
99
- width : c.width,
100
- editor : editor,
101
- renderer : renderer,
102
- sortable : true
103
- }, extConfig));
104
- }
105
-
106
- }, this);
107
-
108
- var cm = new Ext.grid.ColumnModel(this.cmConfig);
109
- cm.on('hiddenchange', this.onColumnHiddenChange, this);
110
-
111
- // Filters
112
- if (config.enableColumnFilters) {
113
- var filters = [];
114
- Ext.each(config.columns, function(c){
115
- filters.push({type:Ext.netzke.filterMap[c.editor], dataIndex:c.name});
116
- });
117
- var gridFilters = new Ext.grid.GridFilters({filters:filters});
118
- plugins.push(gridFilters);
119
- }
120
-
121
- config.bbar = (config.rowsPerPage) ? new Ext.PagingToolbar({
122
- pageSize : config.rowsPerPage,
123
- items : config.bbar ? ["-", config.bbar] : [],
124
- store : ds,
125
- emptyMsg:'Empty'}) : config.bbar
126
-
127
- JS
128
- end
129
-
130
- def js_listeners
131
- super.merge({
132
- :columnresize => {:fn => "this.onColumnResize".l, :scope => this},
133
- :columnmove => {:fn => "this.onColumnMove".l, :scope => this}
134
- })
135
- end
136
-
137
- def js_extend_properties
138
- {
139
- :on_widget_load => <<-JS.l,
140
- function(){
141
- // auto-load
142
- if (this.initialConfig.autoLoadData) {
143
- // if we have a paging toolbar, load the first page
144
- if (this.getBottomToolbar() && this.getBottomToolbar().changePage) {this.getBottomToolbar().changePage(0);} else {this.store.load();}
145
- }
146
- }
147
- JS
148
-
149
- :load_exception_handler => <<-JS.l,
150
- function(proxy, options, response, error){
151
- if (response.status == 200 && (responseObject = Ext.decode(response.responseText)) && responseObject.flash){
152
- this.feedback(responseObject.flash);
153
- } else {
154
- if (error){
155
- this.feedback(error.message);
156
- } else {
157
- this.feedback(response.statusText);
158
- }
159
- }
160
- }
161
- JS
162
-
163
- :add => <<-JS.l,
164
- function(){
165
- var rowConfig = {};
166
- Ext.each(this.initialConfig.columns, function(c){
167
- rowConfig[c.name] = c.defaultValue || ''; // FIXME: if the user is happy with all the defaults, the record won't be 'dirty'
168
- }, this);
169
-
170
- var r = new this.Row(rowConfig); // TODO: add default values
171
- r.is_new = true; // to distinguish new records
172
- r.set('id', r.id); // otherwise later r.get('id') returns empty string
173
- this.stopEditing();
174
- this.store.add(r);
175
- this.tryStartEditing(this.store.indexOf(r));
176
- }
177
- JS
178
-
179
- :edit => <<-JS.l,
180
- function(){
181
- var row = this.getSelectionModel().getSelected();
182
- if (row){
183
- this.tryStartEditing(this.store.indexOf(row));
184
- }
185
- }
186
- JS
187
-
188
- # try editing the first editable (not hidden, not read-only) sell
189
- :try_start_editing => <<-JS.l,
190
- function(row){
191
- if (row === null) {return;}
192
- var editableColumns = this.getColumnModel().getColumnsBy(function(columnConfig, index){
193
- return !columnConfig.hidden && !!columnConfig.editor;
194
- });
195
- var firstEditableColumn = editableColumns[0];
196
- if (firstEditableColumn){
197
- this.startEditing(row, firstEditableColumn.id);
198
- }
199
- }
200
- JS
201
-
202
- :delete => <<-JS.l,
203
- function() {
204
- if (this.getSelectionModel().hasSelection()){
205
- Ext.Msg.confirm('Confirm', 'Are you sure?', function(btn){
206
- if (btn == 'yes') {
207
- var records = [];
208
- this.getSelectionModel().each(function(r){
209
- records.push(r.get('id'));
210
- }, this);
211
- Ext.Ajax.request({
212
- url: this.initialConfig.interface.deleteData,
213
- params: {records: Ext.encode(records)},
214
- success:function(r){
215
- var m = Ext.decode(r.responseText);
216
- this.store.reload();
217
- this.feedback(m.flash);
218
- },
219
- scope:this
220
- });
221
- }
222
- }, this);
223
- }
224
- }
225
- JS
226
- :apply => <<-JS.l,
227
- function(){
228
- var newRecords = [];
229
- var updatedRecords = [];
230
-
231
- Ext.each(this.store.getModifiedRecords(),
232
- function(r) {
233
- if (r.is_new) {
234
- newRecords.push(Ext.apply(r.getChanges(), {id:r.get('id')}));
235
- } else {
236
- updatedRecords.push(Ext.apply(r.getChanges(), {id:r.get('id')}));
237
- }
238
- },
239
- this);
240
-
241
- if (newRecords.length > 0 || updatedRecords.length > 0) {
242
- var params = {};
243
-
244
- if (newRecords.length > 0) {
245
- params.created_records = Ext.encode(newRecords);
246
- }
247
-
248
- if (updatedRecords.length > 0) {
249
- params.updated_records = Ext.encode(updatedRecords);
250
- }
251
-
252
- if (this.store.baseParams !== {}) {
253
- params.base_params = Ext.encode(this.store.baseParams);
254
- }
255
-
256
- Ext.Ajax.request({
257
- url:this.initialConfig.interface.postData,
258
- params: params,
259
- success:function(response){
260
- var m = Ext.decode(response.responseText);
261
- if (m.success) {
262
- // commit those rows that have successfully been updated/created
263
- var modRecords = [].concat(this.store.getModifiedRecords()); // there must be a better way to clone an array...
264
- Ext.each(modRecords, function(r){
265
- var idsToSearch = r.is_new ? m.modRecordIds.create : m.modRecordIds.update;
266
- if (idsToSearch.indexOf(r.id) >= 0) {r.commit();}
267
- });
268
-
269
- // reload the grid only when there were no errors
270
- // (we need to reload because of filtering, sorting, etc)
271
- if (this.store.getModifiedRecords().length === 0){
272
- this.store.reload();
273
- }
274
-
275
- this.feedback(m.flash);
276
- } else {
277
- this.feedback(m.flash);
278
- }
279
- },
280
- failure:function(response){
281
- this.feedback('Bad response from server');
282
- },
283
- scope:this
284
- });
285
- }
286
-
287
- }
288
- JS
289
-
290
- :refresh => <<-JS.l,
291
- function() {
292
- if (this.fireEvent('refresh', this) !== false) {this.store.reload();}
293
- }
294
- JS
295
-
296
- :on_column_resize => <<-JS.l,
297
- function(index, size){
298
- Ext.Ajax.request({
299
- url:this.initialConfig.interface.resizeColumn,
300
- params:{
301
- index:index,
302
- size:size
303
- }
304
- });
305
- }
306
- JS
307
-
308
- :on_column_hidden_change => <<-JS.l,
309
- function(cm, index, hidden){
310
- Ext.Ajax.request({
311
- url:this.initialConfig.interface.hideColumn,
312
- params:{
313
- index:index,
314
- hidden:hidden
315
- }
316
- });
317
- }
318
- JS
319
-
320
- :on_column_move => <<-JS.l
321
- function(oldIndex, newIndex){
322
- Ext.Ajax.request({
323
- url:this.initialConfig.interface.moveColumn,
324
- params:{
325
- old_index:oldIndex,
326
- new_index:newIndex
327
- },
328
- success : function(response){
329
- // we need to reconfigure ArrayReader's recordType in order to correctly interprete
330
- // the new order of data fields coming from the server
331
-
332
- // we receive new record order from the server, which is less error-prone than trying to
333
- // blindly track the column order on the client side
334
- columns = Ext.decode(response.responseText).columns;
335
- columnsInNewOrder = [];
336
- Ext.each(columns, function(c){
337
- columnsInNewOrder.push({name:c});
338
- });
339
- newRecordType = Ext.data.Record.create(columnsInNewOrder);
340
- this.store.reader.recordType = newRecordType; // yes, recordType is a protected property, but that's the only way we can do it, and it seems to work
341
- },
342
- scope : this
343
- });
344
- }
345
- JS
346
-
347
- }
348
- end
349
- end
350
- end
351
- end
352
- end