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,33 @@
1
+ // CheckColumn
2
+ Ext.grid.CheckColumn = function(config){
3
+ Ext.apply(this, config);
4
+ if(!this.id){
5
+ this.id = Ext.id();
6
+ }
7
+ this.renderer = this.renderer.createDelegate(this);
8
+ };
9
+
10
+ Ext.grid.CheckColumn.prototype ={
11
+ init : function(grid){
12
+ this.grid = grid;
13
+ if (this.disabled) {return;} // SK
14
+ this.grid.on('render', function(){
15
+ var view = this.grid.getView();
16
+ view.mainBody.on('mousedown', this.onMouseDown, this);
17
+ }, this);
18
+ },
19
+
20
+ onMouseDown : function(e, t){
21
+ if(t.className && t.className.indexOf('x-grid3-cc-'+this.id) != -1){
22
+ e.stopEvent();
23
+ var index = this.grid.getView().findRowIndex(t);
24
+ var record = this.grid.store.getAt(index);
25
+ record.set(this.dataIndex, !record.data[this.dataIndex]);
26
+ }
27
+ },
28
+
29
+ renderer : function(v, p, record){
30
+ p.css += ' x-grid3-check-col-td';
31
+ return '<div class="x-grid3-check-col'+(v?'-on':'')+' x-grid3-cc-'+this.id+'">&#160;</div>';
32
+ }
33
+ };
@@ -0,0 +1,280 @@
1
+ Ext.namespace('Ext.ux.dd');
2
+
3
+ Ext.ux.dd.GridDragDropRowOrder = Ext.extend(Ext.util.Observable,
4
+ {
5
+ copy: false,
6
+
7
+ scrollable: false,
8
+
9
+ constructor : function(config)
10
+ {
11
+ Ext.ux.dd.GridDragDropRowOrder.superclass.constructor.call(this);
12
+ if (config)
13
+ Ext.apply(this, config);
14
+
15
+ this.addEvents(
16
+ {
17
+ beforerowmove: true,
18
+ afterrowmove: true,
19
+ beforerowcopy: true,
20
+ afterrowcopy: true
21
+ });
22
+ },
23
+
24
+ init : function (grid)
25
+ {
26
+ this.grid = grid;
27
+ grid.enableDragDrop = true;
28
+
29
+ grid.on({
30
+ render: { fn: this.onGridRender, scope: this, single: true }
31
+ });
32
+ },
33
+
34
+ onGridRender : function (grid)
35
+ {
36
+ var self = this;
37
+
38
+ this.target = new Ext.dd.DropTarget(grid.getEl(),
39
+ {
40
+ ddGroup: grid.ddGroup || 'GridDD',
41
+ grid: grid,
42
+ gridDropTarget: this,
43
+
44
+ notifyDrop: function(dd, e, data)
45
+ {
46
+ // Remove drag lines. The 'if' condition prevents null error when drop occurs without dragging out of the selection area
47
+ if (this.currentRowEl)
48
+ {
49
+ this.currentRowEl.removeClass('grid-row-insert-below');
50
+ this.currentRowEl.removeClass('grid-row-insert-above');
51
+ }
52
+
53
+ // determine the row
54
+ var t = Ext.lib.Event.getTarget(e);
55
+ var rindex = this.grid.getView().findRowIndex(t);
56
+ if (rindex === false || rindex == data.rowIndex)
57
+ {
58
+ return false;
59
+ }
60
+ // fire the before move/copy event
61
+ if (this.gridDropTarget.fireEvent(self.copy ? 'beforerowcopy' : 'beforerowmove', this.gridDropTarget, data.rowIndex, rindex, data.selections, 123) === false)
62
+ {
63
+ return false;
64
+ }
65
+
66
+ // update the store
67
+ var ds = this.grid.getStore();
68
+
69
+ // Changes for multiselction by Spirit
70
+ var selections = new Array();
71
+ var keys = ds.data.keys;
72
+ for (var key in keys)
73
+ {
74
+ for (var i = 0; i < data.selections.length; i++)
75
+ {
76
+ if (keys[key] == data.selections[i].id)
77
+ {
78
+ // Exit to prevent drop of selected records on itself.
79
+ if (rindex == key)
80
+ {
81
+ return false;
82
+ }
83
+ selections.push(data.selections[i]);
84
+ }
85
+ }
86
+ }
87
+
88
+ // fix rowindex based on before/after move
89
+ if (rindex > data.rowIndex && this.rowPosition < 0)
90
+ {
91
+ rindex--;
92
+ }
93
+ if (rindex < data.rowIndex && this.rowPosition > 0)
94
+ {
95
+ rindex++;
96
+ }
97
+
98
+ // fix rowindex for multiselection
99
+ if (rindex > data.rowIndex && data.selections.length > 1)
100
+ {
101
+ rindex = rindex - (data.selections.length - 1);
102
+ }
103
+
104
+ // we tried to move this node before the next sibling, we stay in place
105
+ if (rindex == data.rowIndex)
106
+ {
107
+ return false;
108
+ }
109
+
110
+ // fire the before move/copy event
111
+ /* dupe - does it belong here or above???
112
+ if (this.gridDropTarget.fireEvent(self.copy ? 'beforerowcopy' : 'beforerowmove', this.gridDropTarget, data.rowIndex, rindex, data.selections, 123) === false)
113
+ {
114
+ return false;
115
+ }
116
+ */
117
+
118
+ if (!self.copy)
119
+ {
120
+ for (var i = 0; i < data.selections.length; i++)
121
+ {
122
+ ds.remove(ds.getById(data.selections[i].id));
123
+ }
124
+ }
125
+
126
+ for (var i = selections.length - 1; i >= 0; i--)
127
+ {
128
+ var insertIndex = rindex;
129
+ ds.insert(insertIndex, selections[i]);
130
+ }
131
+
132
+ // re-select the row(s)
133
+ var sm = this.grid.getSelectionModel();
134
+ if (sm)
135
+ {
136
+ sm.selectRecords(data.selections);
137
+ }
138
+
139
+ // console.info(this.gridDropTarget);
140
+
141
+ // fire the after move/copy event
142
+ this.gridDropTarget.fireEvent(self.copy ? 'afterrowcopy' : 'afterrowmove', this.gridDropTarget, data.rowIndex, rindex, data.selections);
143
+ return true;
144
+ },
145
+
146
+ notifyOver: function(dd, e, data)
147
+ {
148
+ var t = Ext.lib.Event.getTarget(e);
149
+ var rindex = this.grid.getView().findRowIndex(t);
150
+
151
+ // Similar to the code in notifyDrop. Filters for selected rows and quits function if any one row matches the current selected row.
152
+ var ds = this.grid.getStore();
153
+ var keys = ds.data.keys;
154
+ for (var key in keys)
155
+ {
156
+ for (var i = 0; i < data.selections.length; i++)
157
+ {
158
+ if (keys[key] == data.selections[i].id)
159
+ {
160
+ if (rindex == key)
161
+ {
162
+ if (this.currentRowEl)
163
+ {
164
+ this.currentRowEl.removeClass('grid-row-insert-below');
165
+ this.currentRowEl.removeClass('grid-row-insert-above');
166
+ }
167
+ return this.dropNotAllowed;
168
+ }
169
+ }
170
+ }
171
+ }
172
+
173
+ // If on first row, remove upper line. Prevents negative index error as a result of rindex going negative.
174
+ if (rindex < 0 || rindex === false)
175
+ {
176
+ this.currentRowEl.removeClass('grid-row-insert-above');
177
+ return this.dropNotAllowed;
178
+ }
179
+
180
+ try
181
+ {
182
+ var currentRow = this.grid.getView().getRow(rindex);
183
+ // Find position of row relative to page (adjusting for grid's scroll position)
184
+ var resolvedRow = new Ext.Element(currentRow).getY() - this.grid.getView().scroller.dom.scrollTop;
185
+ var rowHeight = currentRow.offsetHeight;
186
+
187
+ // Cursor relative to a row. -ve value implies cursor is above the row's middle and +ve value implues cursor is below the row's middle.
188
+ this.rowPosition = e.getPageY() - resolvedRow - (rowHeight/2);
189
+
190
+ // Clear drag line.
191
+ if (this.currentRowEl)
192
+ {
193
+ this.currentRowEl.removeClass('grid-row-insert-below');
194
+ this.currentRowEl.removeClass('grid-row-insert-above');
195
+ }
196
+
197
+ if (this.rowPosition > 0)
198
+ {
199
+ // If the pointer is on the bottom half of the row.
200
+ this.currentRowEl = new Ext.Element(currentRow);
201
+ this.currentRowEl.addClass('grid-row-insert-below');
202
+ }
203
+ else
204
+ {
205
+ // If the pointer is on the top half of the row.
206
+ if (rindex - 1 >= 0)
207
+ {
208
+ var previousRow = this.grid.getView().getRow(rindex - 1);
209
+ this.currentRowEl = new Ext.Element(previousRow);
210
+ this.currentRowEl.addClass('grid-row-insert-below');
211
+ }
212
+ else
213
+ {
214
+ // If the pointer is on the top half of the first row.
215
+ this.currentRowEl.addClass('grid-row-insert-above');
216
+ }
217
+ }
218
+ }
219
+ catch (err)
220
+ {
221
+ console.warn(err);
222
+ rindex = false;
223
+ }
224
+ return (rindex === false)? this.dropNotAllowed : this.dropAllowed;
225
+ },
226
+
227
+ notifyOut: function(dd, e, data)
228
+ {
229
+ // Remove drag lines when pointer leaves the gridView.
230
+ if (this.currentRowEl)
231
+ {
232
+ this.currentRowEl.removeClass('grid-row-insert-above');
233
+ this.currentRowEl.removeClass('grid-row-insert-below');
234
+ }
235
+ }
236
+ });
237
+
238
+ if (this.targetCfg)
239
+ {
240
+ Ext.apply(this.target, this.targetCfg);
241
+ }
242
+
243
+ if (this.scrollable)
244
+ {
245
+ Ext.dd.ScrollManager.register(grid.getView().getEditorParent());
246
+ grid.on({
247
+ beforedestroy: this.onBeforeDestroy,
248
+ scope: this,
249
+ single: true
250
+ });
251
+ }
252
+ },
253
+
254
+ getTarget: function()
255
+ {
256
+ return this.target;
257
+ },
258
+
259
+ getGrid: function()
260
+ {
261
+ return this.grid;
262
+ },
263
+
264
+ getCopy: function()
265
+ {
266
+ return this.copy ? true : false;
267
+ },
268
+
269
+ setCopy: function(b)
270
+ {
271
+ this.copy = b ? true : false;
272
+ },
273
+
274
+ onBeforeDestroy : function (grid)
275
+ {
276
+ // if we previously registered with the scroll manager, unregister
277
+ // it (if we don't it will lead to problems in IE)
278
+ Ext.dd.ScrollManager.unregister(grid.getView().getEditorParent());
279
+ }
280
+ });
@@ -0,0 +1,721 @@
1
+ module Netzke
2
+ module GridPanelJs
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ def js_config
8
+ res = super
9
+ res.merge!(:clmns => columns)
10
+ res.merge!(:data_class_name => config[:data_class_name])
11
+ res.merge!(:inline_data => get_data) if ext_config[:load_inline_data]
12
+ res
13
+ end
14
+
15
+ module ClassMethods
16
+
17
+ def js_base_class
18
+ 'Ext.grid.EditorGridPanel'
19
+ end
20
+
21
+ # Ext.Component#initComponent, built up from pices (dependent on class configuration)
22
+ def js_init_component
23
+
24
+ # Optional "edit in form"-related events
25
+ edit_in_form_events = <<-END_OF_JAVASCRIPT if config[:edit_in_form_available]
26
+ if (this.enableEditInForm) {
27
+ this.getSelectionModel().on('selectionchange', function(selModel){
28
+ // Disable "edit in form" button if new record is present in selection
29
+ var disabled = !selModel.each(function(r){
30
+ if (r.isNew) { return false; }
31
+ });
32
+ this.actions.editInForm.setDisabled(disabled);
33
+ }, this);
34
+ }
35
+ END_OF_JAVASCRIPT
36
+
37
+ # Result
38
+ <<-END_OF_JAVASCRIPT
39
+ function(){
40
+ if (!this.clmns) {this.feedback('No columns defined for grid '+this.id);}
41
+
42
+ /* Process columns - all in sake of creating the column model */
43
+ // Normalize columns passed in the config
44
+ var normClmns = [];
45
+ Ext.each(this.clmns, function(c){
46
+ if (!c.excluded) {
47
+ // normalize columns
48
+ if (typeof c == 'string') {
49
+ normClmns.push({name:c});
50
+ } else {
51
+ normClmns.push(c);
52
+ }
53
+ }
54
+ });
55
+
56
+ delete this.clmns; // we don't need them anymore
57
+
58
+ var cmConfig = []; // column model config - we'll use it later to create the ColumnModel
59
+ this.plugins = []; // checkbox colums is a special case, being a plugin
60
+
61
+ var filters = [];
62
+
63
+ // Run through columns
64
+ Ext.each(normClmns, function(c){
65
+ // Apply default column config
66
+ Ext.applyIf(c, this.defaultColumnConfig);
67
+
68
+ // setting dataIndex separately
69
+ c.dataIndex = c.name;
70
+
71
+ // Automatically calculated default values
72
+ if (!c.header) {c.header = c.name.humanize()}
73
+
74
+ // normalize editor
75
+ if (c.editor) {
76
+ c.editor = Netzke.isObject(c.editor) ? c.editor : {xtype:c.editor};
77
+ } else {
78
+ c.editor = {xtype: 'textfield'}
79
+ }
80
+
81
+ // collect filters
82
+ if (c.withFilters){
83
+ filters.push({type:Ext.netzke.filterMap[c.editor.xtype], dataIndex:c.name});
84
+ }
85
+
86
+ if (c.editor && c.editor.xtype == 'checkbox') {
87
+ // Special case of checkbox column
88
+ var plugin = new Ext.grid.CheckColumn(c);
89
+ this.plugins.push(plugin);
90
+ cmConfig.push(plugin);
91
+ } else {
92
+ // "normal" column, not a plugin
93
+ if (!c.readOnly && !this.prohibitUpdate) {
94
+ // c.editor contains complete config of the editor
95
+ var xtype = c.editor.xtype;
96
+ c.editor = Ext.ComponentMgr.create(Ext.apply({
97
+ parentId:this.id,
98
+ name: c.name,
99
+ selectOnFocus:true
100
+ }, c.editor));
101
+ } else {
102
+ c.editor = null;
103
+ }
104
+
105
+ // set the renderer
106
+ c.renderer = Ext.netzke.normalizedRenderer(c.renderer);
107
+
108
+ // add to the list
109
+ cmConfig.push(c);
110
+ }
111
+
112
+ }, this);
113
+
114
+ // Finally, create the ColumnModel based on processed columns
115
+ this.cm = new Ext.grid.ColumnModel(cmConfig);
116
+ this.cm.on('hiddenchange', this.onColumnHiddenChange, this);
117
+
118
+ /* ... and done with columns */
119
+
120
+ // Filters
121
+ if (this.enableColumnFilters) {
122
+ this.plugins.push(new Ext.grid.GridFilters({filters:filters}));
123
+ }
124
+
125
+ // Create Ext.data.Record constructor specific for our particular column configuration
126
+ this.recordConfig = [];
127
+ Ext.each(normClmns, function(column){this.recordConfig.push({name:column.name});}, this);
128
+ this.Row = Ext.data.Record.create(this.recordConfig);
129
+
130
+ // Drag'n'Drop
131
+ if (this.enableRowsReordering){
132
+ this.ddPlugin = new Ext.ux.dd.GridDragDropRowOrder({
133
+ scrollable: true // enable scrolling support (default is false)
134
+ });
135
+ this.plugins.push(this.ddPlugin);
136
+ }
137
+
138
+ // Explicitely create the connection to get grid's data,
139
+ // because we don't want the app-wide Ext.Ajax to be used,
140
+ // as we are going to subscribe to its events
141
+ var connection = new Ext.data.Connection({
142
+ url:this.id+"__get_data",
143
+ extraParams : {
144
+ authenticity_token : Ext.authenticityToken
145
+ },
146
+
147
+ // inform Ext.Ajax about our events
148
+ listeners: {
149
+ beforerequest: function(){
150
+ Ext.Ajax.fireEvent('beforerequest', arguments);
151
+ },
152
+ requestexception: function(){
153
+ Ext.Ajax.fireEvent('requestexception', arguments);
154
+ },
155
+ requestcomplete: function(){
156
+ Ext.Ajax.fireEvent('requestcomplete', arguments);
157
+ }
158
+ }
159
+ });
160
+
161
+ // besides getting data into the store, we may also get commands to execute
162
+ connection.on('requestcomplete', function(conn, r){
163
+ var response = Ext.decode(r.responseText);
164
+
165
+ // delete data-related properties
166
+ Ext.each(['data', 'total', 'success'], function(property){delete response[property];});
167
+ this.bulkExecute(response);
168
+ }, this);
169
+
170
+ // HttpProxy that uses our custom connection
171
+ var httpProxy = new Ext.data.HttpProxy(connection);
172
+
173
+ // Data store
174
+ this.store = new Ext.data.Store({
175
+ proxy: this.proxy = httpProxy,
176
+ reader: new Ext.data.ArrayReader({root: "data", totalProperty: "total", successProperty: "success", id:0}, this.Row),
177
+ remoteSort: true,
178
+ listeners:{'loadexception':{
179
+ fn:this.loadExceptionHandler,
180
+ scope:this
181
+ }}
182
+ });
183
+
184
+ // Normalize bottom bar
185
+ this.bbar = (this.enablePagination) ? new Ext.PagingToolbar({
186
+ pageSize : this.rowsPerPage,
187
+ items : this.bbar ? ["-"].concat(this.bbar) : [],
188
+ store : this.store,
189
+ emptyMsg: 'Empty'
190
+ }) : this.bbar;
191
+
192
+ // Selection model
193
+ this.sm = new Ext.grid.RowSelectionModel();
194
+
195
+ // Now let Ext.grid.EditorGridPanel do the rest
196
+ // Original initComponent
197
+ Ext.netzke.cache.GridPanel.superclass.initComponent.call(this);
198
+
199
+ // Set the events
200
+ this.on('columnresize', this.onColumnResize, this);
201
+ this.on('columnmove', this.onColumnMove, this);
202
+
203
+ // Context menu
204
+ if (this.enableContextMenu) {
205
+ this.on('rowcontextmenu', this.onRowContextMenu, this);
206
+ }
207
+
208
+ // Load data AFTER the toolbar is bound to the store, which will provide for correct page number
209
+ if (this.loadInlineData) {
210
+ this.getStore().loadData(this.inlineData);
211
+
212
+ // If rows per page specified, fake store.lastOptions as if the data was loaded
213
+ // by PagingToolbar (for correct functionning of refresh tool and extended search)
214
+ if (this.rowsPerPage) {
215
+ this.getStore().lastOptions = {params:{limit:this.rowsPerPage, start:0}}; // this is how PagingToolbar does it...
216
+ }
217
+
218
+ // inlineData may also contain commands (TODO: make it DRY)
219
+ // delete data-related properties
220
+ Ext.each(['data', 'total', 'success'], function(property){delete this.inlineData[property];}, this);
221
+ this.bulkExecute(this.inlineData);
222
+ }
223
+
224
+ // Process selectionchange event
225
+ this.getSelectionModel().on('selectionchange', function(selModel){
226
+ // enable/disable actions
227
+ this.actions.del.setDisabled(!selModel.hasSelection() || this.prohibitDelete);
228
+ this.actions.edit.setDisabled(selModel.getCount() != 1 || this.prohibitUpdate);
229
+ }, this);
230
+
231
+ // Drag n Drop event
232
+ if (this.enableRowsReordering){
233
+ this.ddPlugin.on('afterrowmove', this.onAfterRowMove, this);
234
+ }
235
+
236
+ // GridView
237
+ this.getView().getRowClass = this.defaultGetRowClass;
238
+
239
+ #{edit_in_form_events}
240
+ }
241
+
242
+ END_OF_JAVASCRIPT
243
+
244
+ end
245
+
246
+ def js_extend_properties
247
+ res = super
248
+
249
+ # Defaults
250
+ res.merge!(
251
+ {
252
+ :track_mouse_over => true,
253
+ :load_mask => true,
254
+ :auto_scroll => true,
255
+
256
+ :default_column_config => config_columns.inject({}){ |r, c| c.is_a?(Hash) ? r.merge(c[:name] => c[:default]) : r },
257
+
258
+ :init_component => js_init_component.l,
259
+
260
+ :load_exception_handler => <<-END_OF_JAVASCRIPT.l,
261
+ function(proxy, options, response, error){
262
+ if (response.status == 200 && (responseObject = Ext.decode(response.responseText)) && responseObject.flash){
263
+ this.feedback(responseObject.flash);
264
+ } else {
265
+ if (error){
266
+ this.feedback(error.message);
267
+ } else {
268
+ this.feedback(response.statusText);
269
+ }
270
+ }
271
+ }
272
+ END_OF_JAVASCRIPT
273
+
274
+ :update => <<-END_OF_JAVASCRIPT.l,
275
+ function(){
276
+ this.refresh();
277
+ }
278
+ END_OF_JAVASCRIPT
279
+
280
+ :load_store_data => <<-END_OF_JAVASCRIPT.l,
281
+ function(data){
282
+ this.store.loadData(data);
283
+ Ext.each(['data', 'total', 'success'], function(property){delete data[property];}, this);
284
+ this.bulkExecute(data);
285
+ }
286
+ END_OF_JAVASCRIPT
287
+
288
+ :add => <<-END_OF_JAVASCRIPT.l,
289
+ function(){
290
+ var rowConfig = {};
291
+ var r = new this.Row(rowConfig); // TODO: add default values
292
+ r.isNew = true; // to distinguish new records
293
+ r.set('id', r.id); // otherwise later r.get('id') returns empty string
294
+ this.stopEditing();
295
+ this.store.add(r);
296
+ this.tryStartEditing(this.store.indexOf(r));
297
+ }
298
+ END_OF_JAVASCRIPT
299
+
300
+ :edit => <<-END_OF_JAVASCRIPT.l,
301
+ function(){
302
+ var row = this.getSelectionModel().getSelected();
303
+ if (row){
304
+ this.tryStartEditing(this.store.indexOf(row));
305
+ }
306
+ }
307
+ END_OF_JAVASCRIPT
308
+
309
+ # try editing the first editable (i.e. not hidden, not read-only) sell
310
+ :try_start_editing => <<-END_OF_JAVASCRIPT.l,
311
+ function(row){
312
+ if (row === null) {return;}
313
+ var editableColumns = this.getColumnModel().getColumnsBy(function(columnConfig, index){
314
+ return !columnConfig.hidden && !!columnConfig.editor;
315
+ });
316
+ var firstEditableColumn = editableColumns[0];
317
+ if (firstEditableColumn){
318
+ this.startEditing(row, firstEditableColumn.id);
319
+ }
320
+ }
321
+ END_OF_JAVASCRIPT
322
+
323
+ :del => <<-END_OF_JAVASCRIPT.l,
324
+ function() {
325
+ if (this.getSelectionModel().hasSelection()){
326
+ Ext.Msg.confirm('Confirm', 'Are you sure?', function(btn){
327
+ if (btn == 'yes') {
328
+ var records = [];
329
+ this.getSelectionModel().each(function(r){
330
+ if (r.isNew) {
331
+ // this record is not know to server - simply remove from store
332
+ this.store.remove(r);
333
+ } else {
334
+ records.push(r.get('id'));
335
+ }
336
+ }, this);
337
+
338
+ if (records.length > 0){
339
+ // call API
340
+ this.deleteData({records: Ext.encode(records)});
341
+ }
342
+ }
343
+ }, this);
344
+ }
345
+ }
346
+ END_OF_JAVASCRIPT
347
+
348
+ # Called by the server side to update newly created records
349
+ :update_new_records => <<-END_OF_JAVASCRIPT.l,
350
+ function(records){
351
+ this.updateRecords(records);
352
+ }
353
+ END_OF_JAVASCRIPT
354
+
355
+ # Called by the server side to update modified records
356
+ :update_mod_records => <<-END_OF_JAVASCRIPT.l,
357
+ function(records){
358
+ this.updateRecords(records, true);
359
+ }
360
+ END_OF_JAVASCRIPT
361
+
362
+ # Updates modified or newly created records
363
+ # Example of the records argument:
364
+ # {1098 => [1, 'value1', 'value2'], 1099 => [2, 'value1', 'value2']}
365
+ :update_records => <<-END_OF_JAVASCRIPT.l,
366
+ function(records, mod){
367
+ if (!mod) {mod = false;}
368
+ var modRecordsInGrid = [].concat(this.store.getModifiedRecords()); // there must be a better way to clone an array...
369
+
370
+ // replace arrays of data in the args object with Ext.data.Record objects
371
+ for (var k in records){
372
+ records[k] = this.store.reader.readRecords([records[k]]).records[0];
373
+ }
374
+
375
+ // for each new record write the data returned by the server, and commit the record
376
+ Ext.each(modRecordsInGrid, function(recordInGrid){
377
+ if (mod ^ recordInGrid.isNew) {
378
+ // new data that the server sent us to update this record
379
+ var newData = records[recordInGrid.get('id')];
380
+
381
+ if (newData){
382
+ for (var k in newData.data){
383
+ recordInGrid.set(k, newData.get(k));
384
+ }
385
+
386
+ recordInGrid.isNew = false;
387
+ recordInGrid.commit();
388
+ }
389
+
390
+ }
391
+ });
392
+
393
+ // clear the selections
394
+ this.getSelectionModel().clearSelections();
395
+
396
+ // check if there are still records with errors
397
+ var modRecords = this.store.getModifiedRecords();
398
+ if (modRecords.length == 0) {
399
+ // if all records are accepted, reload the grid (so that eventual order/filtering is correct)
400
+ this.store.reload();
401
+
402
+ // ... and set default getRowClass function
403
+ this.getView().getRowClass = this.defaultGetRowClass;
404
+ } else {
405
+ this.getView().getRowClass = function(r){
406
+ return r.dirty ? "grid-dirty-record" : ""
407
+ }
408
+ }
409
+
410
+ this.getView().refresh();
411
+ }
412
+ END_OF_JAVASCRIPT
413
+
414
+ :default_get_row_class => <<-END_OF_JAVASCRIPT.l,
415
+ function(r){
416
+ return r.isNew ? "grid-dirty-record" : ""
417
+ }
418
+ END_OF_JAVASCRIPT
419
+
420
+ :apply => <<-END_OF_JAVASCRIPT.l,
421
+ function(){
422
+ var newRecords = [];
423
+ var updatedRecords = [];
424
+
425
+ Ext.each(this.store.getModifiedRecords(),
426
+ function(r) {
427
+ if (r.isNew) {
428
+ newRecords.push(Ext.apply(r.getChanges(), {id:r.get('id')}));
429
+ } else {
430
+ updatedRecords.push(Ext.apply(r.getChanges(), {id:r.get('id')}));
431
+ }
432
+ },
433
+ this);
434
+
435
+ if (newRecords.length > 0 || updatedRecords.length > 0) {
436
+ var params = {};
437
+
438
+ if (newRecords.length > 0) {
439
+ params.created_records = Ext.encode(newRecords);
440
+ }
441
+
442
+ if (updatedRecords.length > 0) {
443
+ params.updated_records = Ext.encode(updatedRecords);
444
+ }
445
+
446
+ if (this.store.baseParams !== {}) {
447
+ params.base_params = Ext.encode(this.store.baseParams);
448
+ }
449
+
450
+ this.postData(params);
451
+ }
452
+
453
+ }
454
+ END_OF_JAVASCRIPT
455
+
456
+ :select_first_row => <<-END_OF_JAVASCRIPT.l,
457
+ function(){
458
+ this.getSelectionModel().suspendEvents();
459
+ this.getSelectionModel().selectRow(0);
460
+ this.getSelectionModel().resumeEvents();
461
+ }
462
+ END_OF_JAVASCRIPT
463
+
464
+ :refresh => <<-END_OF_JAVASCRIPT.l,
465
+ function() {
466
+ if (this.fireEvent('refresh', this) !== false) {
467
+ this.store.reload();
468
+ }
469
+ }
470
+ END_OF_JAVASCRIPT
471
+
472
+ :on_column_resize => <<-END_OF_JAVASCRIPT.l,
473
+ function(index, size){
474
+ this.resizeColumn({
475
+ index:index,
476
+ size:size
477
+ });
478
+ }
479
+ END_OF_JAVASCRIPT
480
+
481
+ :on_column_hidden_change => <<-END_OF_JAVASCRIPT.l,
482
+ function(cm, index, hidden){
483
+ this.hideColumn({
484
+ index:index,
485
+ hidden:hidden
486
+ });
487
+ }
488
+ END_OF_JAVASCRIPT
489
+
490
+ # :reorder_columns => <<-END_OF_JAVASCRIPT.l,
491
+ # function(columns){
492
+ # columnsInNewShipment = [];
493
+ # Ext.each(columns, function(c){
494
+ # columnsInNewShipment.push({name:c});
495
+ # });
496
+ # newRecordType = Ext.data.Record.create(columnsInNewShipment);
497
+ # 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 for now
498
+ # }
499
+ # END_OF_JAVASCRIPT
500
+
501
+ :on_column_move => <<-END_OF_JAVASCRIPT.l,
502
+ function(oldIndex, newIndex){
503
+ this.moveColumn({
504
+ old_index:oldIndex,
505
+ new_index:newIndex
506
+ });
507
+ }
508
+ END_OF_JAVASCRIPT
509
+
510
+ :on_row_context_menu => <<-END_OF_JAVASCRIPT.l,
511
+ function(grid, rowIndex, e){
512
+ e.stopEvent();
513
+ var coords = e.getXY();
514
+
515
+ if (!grid.getSelectionModel().isSelected(rowIndex)) {
516
+ grid.getSelectionModel().selectRow(rowIndex);
517
+ }
518
+
519
+ var menu = new Ext.menu.Menu({
520
+ items: this.contextMenu
521
+ });
522
+
523
+ menu.showAt(coords);
524
+ }
525
+ END_OF_JAVASCRIPT
526
+
527
+ :on_after_row_move => <<-END_OF_JAVASCRIPT.l,
528
+ function(dt, oldIndex, newIndex, records){
529
+ var ids = [];
530
+ // collect records ids
531
+ Ext.each(records, function(r){ids.push(r.get('id'))});
532
+ // call GridPanel's API
533
+ this.moveRows({ids:Ext.encode(ids), new_index: newIndex});
534
+ }
535
+ END_OF_JAVASCRIPT
536
+ }
537
+ )
538
+
539
+ # Edit in form
540
+ res.merge!(
541
+ {
542
+ :on_successfull_record_creation => <<-END_OF_JAVASCRIPT.l,
543
+ function(){
544
+ this.formWindow.hide();
545
+ this.getStore().reload();
546
+ }
547
+ END_OF_JAVASCRIPT
548
+
549
+ :on_successfull_edit => <<-END_OF_JAVASCRIPT.l,
550
+ function(){
551
+ this.editFormWindow.close();
552
+ delete this.editFormWindow;
553
+ this.getStore().reload();
554
+ }
555
+ END_OF_JAVASCRIPT
556
+
557
+ :edit_in_form => <<-END_OF_JAVASCRIPT.l,
558
+ function(){
559
+ // create the window
560
+ delete this.editFormWindow;
561
+ this.editFormWindow = new Ext.Window({
562
+ title: 'Edit',
563
+ layout: 'fit',
564
+ modal: true,
565
+ width: 400,
566
+ height: Ext.lib.Dom.getViewHeight() *0.9,
567
+ buttons:[{
568
+ text: 'OK',
569
+ handler: function(){
570
+ this.ownerCt.getWidget().apply();
571
+ }
572
+ },{
573
+ text:'Cancel',
574
+ handler:function(){
575
+ this.ownerCt.close();
576
+ }
577
+ }]
578
+ });
579
+
580
+ // show it and load the correct aggregatee in it
581
+ this.editFormWindow.show(null, function(){
582
+ var selModel = this.getSelectionModel();
583
+ if (selModel.getCount() > 1) {
584
+ this.editFormWindow.setTitle('Multi-edit');
585
+ // multiedit
586
+ this.loadAggregatee({
587
+ id: "multi_edit_form",
588
+ container: this.editFormWindow.id,
589
+ callback: function(aggr){
590
+ aggr.on('apply', function(){
591
+ var ids = [];
592
+ selModel.each(function(r){
593
+ ids.push(r.get('id'));
594
+ });
595
+ aggr.baseParams = {ids: Ext.encode(ids)}
596
+ }, this);
597
+ },
598
+ scope: this
599
+ });
600
+ } else {
601
+ // single edit
602
+ this.editFormWindow.setTitle('Edit');
603
+ var recordId = selModel.getSelected().get('id');
604
+ this.loadAggregatee({
605
+ id: "edit_form",
606
+ container: this.editFormWindow.id,
607
+ scope: this,
608
+ record_id: recordId
609
+ });
610
+ }
611
+ }, this);
612
+
613
+ }
614
+ END_OF_JAVASCRIPT
615
+
616
+ :add_in_form => <<-END_OF_JAVASCRIPT.l,
617
+ function(){
618
+ if (!this.formWindow) {
619
+ this.formWindow = new Ext.Window({
620
+ title:'Add',
621
+ layout: 'fit',
622
+ modal: true,
623
+ width: 400,
624
+ height: Ext.lib.Dom.getViewHeight() *0.9,
625
+ closeAction: 'hide',
626
+ buttons:[{
627
+ text: 'OK',
628
+ handler: function(){
629
+ this.ownerCt.getWidget().apply();
630
+ }
631
+ },{
632
+ text:'Cancel',
633
+ handler:function(){
634
+ this.ownerCt.close();
635
+ }
636
+ }]
637
+ });
638
+ }
639
+
640
+ this.formWindow.show(null, function(){
641
+ this.formWindow.closeRes = 'cancel';
642
+ if (!this.formWindow.getWidget()){
643
+ this.loadAggregatee({id:"new_record_form", container:this.formWindow.id});
644
+ }
645
+ }, this);
646
+
647
+ }
648
+ END_OF_JAVASCRIPT
649
+
650
+ }
651
+ ) if config[:edit_in_form_available]
652
+
653
+ # Extended search
654
+ res.merge!(
655
+ {
656
+ :search => <<-END_OF_JAVASCRIPT.l,
657
+ function(){
658
+ if (!this.searchWindow){
659
+ this.searchWindow = new Ext.Window({
660
+ title:'Advanced search',
661
+ layout:'fit',
662
+ modal: true,
663
+ width: 400,
664
+ height: Ext.lib.Dom.getViewHeight() *0.9,
665
+ closeAction:'hide',
666
+ buttons:[{
667
+ text: 'OK',
668
+ handler: function(){
669
+ this.ownerCt.closePositively();
670
+ }
671
+ },{
672
+ text:'Cancel',
673
+ handler:function(){
674
+ this.ownerCt.closeNegatively();
675
+ }
676
+ }],
677
+ closePositively : function(){
678
+ this.conditions = this.getWidget().getForm().getValues();
679
+ this.closeRes = 'OK';
680
+ this.hide();
681
+ },
682
+ closeNegatively: function(){
683
+ this.closeRes = 'cancel';
684
+ this.hide();
685
+ }
686
+
687
+ });
688
+
689
+ this.searchWindow.on('hide', function(){
690
+ if (this.searchWindow.closeRes == 'OK'){
691
+ this.getStore().baseParams = {extra_conditions: Ext.encode(this.searchWindow.conditions)};
692
+ this.getStore().reload();
693
+ }
694
+ }, this);
695
+
696
+ this.searchWindow.on('add', function(container, searchPanel){
697
+ searchPanel.on('apply', function(widget){
698
+ this.searchWindow.closePositively();
699
+ return false; // stop the event
700
+ }, this);
701
+ }, this);
702
+ }
703
+
704
+ this.searchWindow.show(null, function(){
705
+ this.searchWindow.closeRes = 'cancel';
706
+ if (!this.searchWindow.getWidget()){
707
+ this.loadAggregatee({id:"search_panel", container:this.searchWindow.id});
708
+ }
709
+ }, this);
710
+
711
+ }
712
+ END_OF_JAVASCRIPT
713
+
714
+ }
715
+ ) if config[:extended_search_available]
716
+
717
+ res
718
+ end
719
+ end
720
+ end
721
+ end