skozlov-netzke-basepack 0.1.1.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. data/.autotest +1 -0
  2. data/.gitignore +5 -0
  3. data/LICENSE +2 -19
  4. data/README.rdoc +87 -0
  5. data/Rakefile +28 -12
  6. data/TODO.rdoc +7 -0
  7. data/VERSION +1 -0
  8. data/autotest/discover.rb +3 -0
  9. data/init.rb +0 -1
  10. data/javascripts/basepack.js +839 -49
  11. data/lib/app/models/netzke_auto_column.rb +56 -0
  12. data/lib/netzke/accordion_panel.rb +113 -0
  13. data/lib/netzke/active_record/basepack.rb +104 -0
  14. data/lib/netzke/active_record/data_accessor.rb +21 -0
  15. data/lib/netzke/basic_app.rb +325 -0
  16. data/lib/netzke/border_layout_panel.rb +128 -0
  17. data/lib/netzke/configuration_panel.rb +24 -0
  18. data/lib/netzke/data_accessor.rb +71 -0
  19. data/lib/netzke/ext.rb +6 -0
  20. data/lib/netzke/field_model.rb +131 -0
  21. data/lib/netzke/fields_configurator.rb +95 -0
  22. data/lib/netzke/form_panel.rb +214 -0
  23. data/lib/netzke/form_panel_api.rb +74 -0
  24. data/lib/netzke/form_panel_extras/javascripts/xcheckbox.js +82 -0
  25. data/lib/netzke/form_panel_js.rb +129 -0
  26. data/lib/netzke/grid_panel.rb +442 -0
  27. data/lib/netzke/grid_panel_api.rb +352 -0
  28. data/lib/netzke/grid_panel_extras/javascripts/check-column.js +33 -0
  29. data/{javascripts → lib/netzke/grid_panel_extras/javascripts}/filters.js +0 -0
  30. data/lib/netzke/grid_panel_extras/javascripts/rows-dd.js +280 -0
  31. data/lib/netzke/grid_panel_js.rb +721 -0
  32. data/lib/netzke/panel.rb +13 -0
  33. data/lib/netzke/plugins/configuration_tool.rb +121 -0
  34. data/lib/netzke/property_editor.rb +105 -0
  35. data/lib/netzke/property_editor_extras/helper_model.rb +126 -0
  36. data/lib/netzke/search_panel.rb +62 -0
  37. data/lib/netzke/tab_panel.rb +160 -0
  38. data/lib/netzke/table_editor.rb +118 -0
  39. data/lib/netzke/tree_panel.rb +73 -0
  40. data/lib/netzke/wrapper.rb +42 -0
  41. data/lib/netzke-basepack.rb +9 -15
  42. data/netzke-basepack.gemspec +147 -20
  43. data/stylesheets/basepack.css +26 -0
  44. data/test/app_root/app/models/book.rb +1 -1
  45. data/test/app_root/config/environment.rb +1 -0
  46. data/test/app_root/db/migrate/20081222033440_create_genres.rb +1 -0
  47. data/test/app_root/db/migrate/20081222035855_create_netzke_preferences.rb +1 -1
  48. data/test/app_root/db/migrate/20090102223630_create_netzke_layouts.rb +14 -0
  49. data/test/app_root/vendor/plugins/acts_as_list/README +23 -0
  50. data/test/app_root/vendor/plugins/acts_as_list/init.rb +3 -0
  51. data/test/app_root/vendor/plugins/acts_as_list/lib/active_record/acts/list.rb +256 -0
  52. data/test/test_helper.rb +1 -2
  53. data/test/unit/accordion_panel_test.rb +20 -0
  54. data/test/unit/active_record_basepack_test.rb +54 -0
  55. data/test/unit/grid_panel_test.rb +43 -0
  56. data/test/unit/helper_model_test.rb +30 -0
  57. data/test/unit/netzke_basepack_test.rb +4 -0
  58. data/test/unit/tab_panel_test.rb +21 -0
  59. metadata +96 -72
  60. data/CHANGELOG +0 -14
  61. data/Manifest +0 -65
  62. data/README.mdown +0 -18
  63. data/generators/netzke_basepack/USAGE +0 -8
  64. data/generators/netzke_basepack/netzke_basepack_generator.rb +0 -8
  65. data/generators/netzke_basepack/netzke_grid_generator.rb +0 -7
  66. data/generators/netzke_basepack/templates/create_netzke_grid_columns.rb +0 -21
  67. data/lib/app/models/netzke_grid_column.rb +0 -23
  68. data/lib/netzke/accordion.rb +0 -11
  69. data/lib/netzke/ar_ext.rb +0 -163
  70. data/lib/netzke/column.rb +0 -43
  71. data/lib/netzke/container.rb +0 -81
  72. data/lib/netzke/grid.rb +0 -120
  73. data/lib/netzke/grid_interface.rb +0 -156
  74. data/lib/netzke/grid_js_builder.rb +0 -276
  75. data/lib/netzke/preference_grid.rb +0 -43
  76. data/lib/netzke/properties_tool.rb +0 -66
  77. data/lib/netzke/property_grid.rb +0 -60
  78. data/test/ar_ext_test.rb +0 -39
  79. data/test/column_test.rb +0 -27
  80. data/test/grid_test.rb +0 -43
  81. data/test/netzke_basepack_test.rb +0 -8
@@ -0,0 +1,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