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