netzke-basepack 0.5.12 → 0.5.13

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,65 @@
1
+ Ext.override(Netzke.pre.GridPanel, {
2
+ onSearch: function(){
3
+ delete this.searchWindow;
4
+ this.searchWindow = new Ext.Window({
5
+ title:'Advanced search',
6
+ layout:'fit',
7
+ modal: true,
8
+ width: 400,
9
+ height: Ext.lib.Dom.getViewHeight() *0.9,
10
+ closeAction:'close',
11
+ buttons:[{
12
+ text: 'OK',
13
+ handler: function(){
14
+ this.ownerCt.ownerCt.closePositively();
15
+ }
16
+ },{
17
+ text:'Cancel',
18
+ handler:function(){
19
+ this.ownerCt.ownerCt.closeNegatively();
20
+ }
21
+ }],
22
+ closePositively : function(){
23
+ this.conditions = this.getWidget().getForm().getValues();
24
+ this.closeRes = 'OK';
25
+ this.close();
26
+ },
27
+ closeNegatively: function(){
28
+ this.closeRes = 'cancel';
29
+ this.close();
30
+ }
31
+ });
32
+
33
+ this.searchWindow.on('close', function(){
34
+ if (this.searchWindow.closeRes == 'OK'){
35
+ var searchConditions = this.searchWindow.conditions;
36
+ var filtered = false;
37
+ // check if there's any search condition set
38
+ for (var k in searchConditions) {
39
+ if (searchConditions[k].length > 0) {
40
+ filtered = true;
41
+ break;
42
+ }
43
+ }
44
+ this.actions.search.setText(filtered ? "Search *" : "Search");
45
+ this.getStore().baseParams = {extra_conditions: Ext.encode(this.searchWindow.conditions)};
46
+ this.getStore().load();
47
+ }
48
+ }, this);
49
+
50
+ this.searchWindow.on('add', function(container, searchPanel){
51
+ searchPanel.on('apply', function(widget){
52
+ this.searchWindow.closePositively();
53
+ return false; // stop the event
54
+ }, this);
55
+ }, this);
56
+
57
+ this.searchWindow.show(null, function(){
58
+ this.searchWindow.closeRes = 'cancel';
59
+ if (!this.searchWindow.getWidget()){
60
+ this.loadAggregatee({id:"searchPanel", container:this.searchWindow.id});
61
+ }
62
+ }, this);
63
+
64
+ }
65
+ });
@@ -0,0 +1,47 @@
1
+ Ext.override(Netzke.pre.GridPanel, {
2
+ onEditInForm: function(){
3
+ var selModel = this.getSelectionModel();
4
+ if (selModel.getCount() > 1) {
5
+ var recordId = selModel.getSelected().id;
6
+ this.loadAggregatee({id: "multiEditForm",
7
+ params: {record_id: recordId},
8
+ callback: function(w){
9
+ var form = w.items.first();
10
+ form.on('apply', function(){
11
+ var ids = [];
12
+ selModel.each(function(r){
13
+ ids.push(r.id);
14
+ });
15
+ form.baseParams = {ids: Ext.encode(ids)}
16
+ }, this);
17
+
18
+ w.on('close', function(){
19
+ if (w.closeRes === "ok") {
20
+ this.store.reload();
21
+ }
22
+ }, this);
23
+ }, scope: this});
24
+ } else {
25
+ var recordId = selModel.getSelected().id;
26
+ this.loadAggregatee({id: "editForm",
27
+ params: {record_id: recordId},
28
+ callback: function(form){
29
+ form.on('close', function(){
30
+ if (form.closeRes === "ok") {
31
+ this.store.reload();
32
+ }
33
+ }, this);
34
+ }, scope: this});
35
+ }
36
+ },
37
+
38
+ onAddInForm: function(){
39
+ this.loadAggregatee({id: "addForm", callback: function(form){
40
+ form.on('close', function(){
41
+ if (form.closeRes === "ok") {
42
+ this.store.reload();
43
+ }
44
+ }, this);
45
+ }, scope: this});
46
+ }
47
+ });
@@ -0,0 +1,562 @@
1
+ /*
2
+ Static part of GridPanel's JavaScript class.
3
+ */
4
+ Netzke.pre.GridPanel = Ext.extend(Ext.grid.EditorGridPanel, {
5
+ trackMouseOver: true,
6
+ loadMask: true,
7
+ autoScroll: true,
8
+
9
+ initComponent: function(){
10
+ if (!this.clmns) {this.feedback('No columns defined for grid '+this.id);}
11
+
12
+ /* Process columns - all in sake of creating the column model */
13
+ // Normalize columns passed in the config
14
+ var normClmns = [];
15
+ Ext.each(this.clmns, function(c){
16
+ // normalize columns
17
+ if (typeof c == 'string') {
18
+ normClmns.push({name:c});
19
+ } else {
20
+ normClmns.push(c);
21
+ }
22
+ });
23
+
24
+ delete this.clmns; // we don't need them anymore
25
+
26
+ var cmConfig = []; // column model config - we'll use it later to create the ColumnModel
27
+ this.plugins = []; // checkbox colums is a special case, being a plugin
28
+
29
+ var filters = [];
30
+
31
+ // Run through columns and set up different configuration for each
32
+ Ext.each(normClmns, function(c){
33
+ // We will not use meta columns as actual columns (not even hidden) - only to create the records
34
+ if (c.meta) return;
35
+
36
+ // Apply default column config
37
+ Ext.applyIf(c, this.defaultColumnConfig);
38
+
39
+ // setting dataIndex separately
40
+ c.dataIndex = c.name;
41
+
42
+ // Automatically calculated default values
43
+ if (!c.header) {c.header = c.label || c.name.humanize()}
44
+
45
+ // normalize editor
46
+ if (c.editor) {
47
+ c.editor = Netzke.isObject(c.editor) ? c.editor : {xtype:c.editor};
48
+ } else {
49
+ c.editor = {xtype: this.attrTypeEditorMap[c.attrType] || 'textfield'}
50
+ }
51
+
52
+ // if comboboxOptions are provided, we render a combobox instead of textfield
53
+ if (c.comboboxOptions && c.editor.xtype === "textfield") {
54
+ c.editor = {xtype: "combobox", options: c.comboboxOptions.split('\\n')}
55
+ }
56
+
57
+ // collect filters
58
+ if (c.filterable){
59
+ filters.push({type:this.filterTypeForAttrType(c.attrType), dataIndex:c.name});
60
+ }
61
+
62
+ if (c.editor && c.editor.xtype == 'checkbox') {
63
+ // Special case of checkbox column
64
+ var plugin = new Ext.ux.grid.CheckColumn(c);
65
+ this.plugins.push(plugin);
66
+ cmConfig.push(plugin);
67
+ } else {
68
+ // a "normal" column, not a plugin
69
+ if (!c.readOnly && !this.prohibitUpdate) {
70
+ // c.editor contains complete config of the editor
71
+ var xtype = c.editor.xtype;
72
+ c.editor = Ext.ComponentMgr.create(Ext.apply({
73
+ parentId: this.id,
74
+ name: c.name,
75
+ selectOnFocus:true
76
+ }, c.editor));
77
+ } else {
78
+ c.editor = null;
79
+ }
80
+
81
+ // Normalize the renderer
82
+ this.normalizeRenderer(c);
83
+
84
+ // set the renderer
85
+ // if (c.renderer && !Ext.isArray(c.renderer) && c.renderer.match(/^\\s*function\\s*\\(/)) {
86
+ // // if the renderer is an inline function - eval it (double escaping because we are inside of the Ruby string here...)
87
+ // eval("c.renderer = " + c.renderer + ";");
88
+ // } else if (Ext.isFunction(this[c.renderer])) {
89
+ // // whether the renderer is defined in this.scope
90
+ // c.renderer = this[c.renderer].createDelegate(this);
91
+ // } else {
92
+ // // othrewise it's a string representing the name of the renderer or a json-encoded array,
93
+ // // where the first parameter is the renderer's name, and the rest - parameters that should be
94
+ // // passed to the renderer at the moment of calling
95
+ // var renderer = Ext.netzke.normalizedRenderer(c.renderer);
96
+ // if (renderer != null) {
97
+ // c.renderer = renderer
98
+ // };
99
+ // }
100
+ //
101
+ // add to the list
102
+ cmConfig.push(c);
103
+ }
104
+
105
+ }, this);
106
+
107
+ // Finally, create the ColumnModel based on processed columns
108
+ this.cm = new Ext.grid.ColumnModel(cmConfig);
109
+
110
+ // Hidden change event
111
+ if (this.persistentConfig) {this.cm.on('hiddenchange', this.onColumnHiddenChange, this);}
112
+
113
+ /* ... and done with columns */
114
+
115
+ // Filters
116
+ if (this.enableColumnFilters) {
117
+ this.plugins.push(new Ext.ux.grid.GridFilters({filters:filters}));
118
+ }
119
+
120
+ // Create Ext.data.Record constructor specific for our particular column configuration
121
+ this.recordConfig = [];
122
+ Ext.each(normClmns, function(column){this.recordConfig.push({name:column.name, defaultValue:column.defaultValue});}, this);
123
+ this.Row = Ext.data.Record.create(this.recordConfig);
124
+
125
+ // Drag'n'Drop
126
+ if (this.enableRowsReordering){
127
+ this.ddPlugin = new Ext.ux.dd.GridDragDropRowOrder({
128
+ scrollable: true // enable scrolling support (default is false)
129
+ });
130
+ this.plugins.push(this.ddPlugin);
131
+ }
132
+
133
+ // Explicitely create the connection to get grid's data,
134
+ // because we don't want the app-wide Ext.Ajax to be used,
135
+ // as we are going to subscribe to its events
136
+ var connection = new Ext.data.Connection({
137
+ url: this.buildApiUrl("get_data"),
138
+ extraParams: {
139
+ authenticity_token : Netzke.authenticityToken
140
+ },
141
+
142
+ // inform Ext.Ajax about our events
143
+ listeners: {
144
+ beforerequest: function(){
145
+ Ext.Ajax.fireEvent('beforerequest', arguments);
146
+ },
147
+ requestexception: function(){
148
+ Ext.Ajax.fireEvent('requestexception', arguments);
149
+ },
150
+ requestcomplete: function(){
151
+ Ext.Ajax.fireEvent('requestcomplete', arguments);
152
+ }
153
+ }
154
+ });
155
+
156
+ // besides getting data into the store, we may also get commands to execute
157
+ connection.on('requestcomplete', function(conn, r){
158
+ var response = Ext.decode(r.responseText);
159
+
160
+ // delete data-related properties
161
+ Ext.each(['data', 'total', 'success'], function(property){delete response[property];});
162
+ this.bulkExecute(response);
163
+ }, this);
164
+
165
+ // HttpProxy that uses our custom connection
166
+ var httpProxy = new Ext.data.HttpProxy(connection);
167
+
168
+ // Data store
169
+ this.store = new Ext.data.Store({
170
+ proxy: this.proxy = httpProxy,
171
+ reader: new Ext.data.ArrayReader({root: "data", totalProperty: "total", successProperty: "success", id:0}, this.Row),
172
+ remoteSort: true,
173
+ listeners:{'loadexception':{
174
+ fn:this.loadExceptionHandler,
175
+ scope:this
176
+ }}
177
+ });
178
+
179
+ // Normalize bottom bar
180
+ this.bbar = (this.enablePagination) ? new Ext.PagingToolbar({
181
+ pageSize : this.rowsPerPage,
182
+ items : this.bbar ? ["-"].concat(this.bbar) : [],
183
+ store : this.store,
184
+ emptyMsg: 'Empty',
185
+ displayInfo: true
186
+ }) : this.bbar;
187
+
188
+ // Selection model
189
+ this.sm = new Ext.grid.RowSelectionModel();
190
+
191
+ // Now let Ext.grid.EditorGridPanel do the rest
192
+ // Original initComponent
193
+ Netzke.pre.GridPanel.superclass.initComponent.call(this);
194
+
195
+ // Inform the server part about column operations
196
+ if (this.persistentConfig) {
197
+ this.on('columnresize', this.onColumnResize, this);
198
+ this.on('columnmove', this.onColumnMove, this);
199
+ }
200
+
201
+ // Context menu
202
+ if (this.enableContextMenu) {
203
+ this.on('rowcontextmenu', this.onRowContextMenu, this);
204
+ }
205
+
206
+ // Load data AFTER the toolbar is bound to the store, which will provide for correct page number
207
+ if (this.loadInlineData) {
208
+ this.getStore().loadData(this.inlineData);
209
+
210
+ // If rows per page specified, fake store.lastOptions as if the data was loaded
211
+ // by PagingToolbar (for correct functionning of refresh tool and extended search)
212
+ if (this.rowsPerPage) {
213
+ this.getStore().lastOptions = {params:{limit:this.rowsPerPage, start:0}}; // this is how PagingToolbar does it...
214
+ }
215
+
216
+ // inlineData may also contain commands (TODO: make it DRY)
217
+ // delete data-related properties
218
+ Ext.each(['data', 'total', 'success'], function(property){delete this.inlineData[property];}, this);
219
+ this.bulkExecute(this.inlineData);
220
+ }
221
+
222
+ // Process selectionchange event
223
+ this.getSelectionModel().on('selectionchange', function(selModel){
224
+ // enable/disable actions
225
+ this.actions.del.setDisabled(!selModel.hasSelection() || this.prohibitDelete);
226
+ this.actions.edit.setDisabled(selModel.getCount() != 1 || this.prohibitUpdate);
227
+ }, this);
228
+
229
+ // Drag n Drop event
230
+ if (this.enableRowsReordering){
231
+ this.ddPlugin.on('afterrowmove', this.onAfterRowMove, this);
232
+ }
233
+
234
+ // GridView
235
+ this.getView().getRowClass = this.defaultGetRowClass;
236
+ },
237
+
238
+ filterTypeForAttrType: function(attrType){
239
+ var map = {
240
+ integer :'Numeric',
241
+ decimal :'Numeric',
242
+ datetime:'Date',
243
+ date :'Date',
244
+ string :'String'
245
+ };
246
+ map['boolean'] = "Boolean"; // "boolean" is a JS reserved word
247
+ return map[attrType] || 'String';
248
+ },
249
+
250
+ attrTypeEditorMap: {
251
+ integer : "numberfield",
252
+ "boolean": "checkbox",
253
+ decimal : "numberfield",
254
+ datetime : "xdatetime",
255
+ date : "datefield",
256
+ string : "textfield"
257
+ },
258
+
259
+ onAdd: function(){
260
+ var r = new this.Row();
261
+ r.isNew = true; // to distinguish new records
262
+ // r.set('id', r.id); // otherwise later r.get('id') returns empty string
263
+ this.stopEditing();
264
+ this.getStore().add(r);
265
+
266
+ // Set default values
267
+ this.getStore().fields.each(function(field){
268
+ r.set(field.name, field.defaultValue);
269
+ });
270
+
271
+ this.tryStartEditing(this.store.indexOf(r));
272
+ },
273
+
274
+ onDel: function() {
275
+ Ext.Msg.confirm('Confirm', 'Are you sure?', function(btn){
276
+ if (btn == 'yes') {
277
+ var records = [];
278
+ this.getSelectionModel().each(function(r){
279
+ if (r.isNew) {
280
+ // this record is not know to server - simply remove from store
281
+ this.store.remove(r);
282
+ } else {
283
+ records.push(r.id);
284
+ }
285
+ }, this);
286
+
287
+ if (records.length > 0){
288
+ // call API
289
+ this.deleteData({records: Ext.encode(records)});
290
+ }
291
+ }
292
+ }, this);
293
+ },
294
+
295
+ onApply: function(){
296
+ var newRecords = [];
297
+ var updatedRecords = [];
298
+ Ext.each(this.store.getModifiedRecords(),
299
+ function(r) {
300
+ if (r.isNew) {
301
+ newRecords.push(Ext.apply(r.getChanges(), {id:r.id}));
302
+ } else {
303
+ updatedRecords.push(Ext.apply(r.getChanges(), {id:r.id}));
304
+ }
305
+ },
306
+ this);
307
+
308
+ if (newRecords.length > 0 || updatedRecords.length > 0) {
309
+ var params = {};
310
+
311
+ if (newRecords.length > 0) {
312
+ params.created_records = Ext.encode(newRecords);
313
+ }
314
+
315
+ if (updatedRecords.length > 0) {
316
+ params.updated_records = Ext.encode(updatedRecords);
317
+ }
318
+
319
+ if (this.store.baseParams !== {}) {
320
+ params.base_params = Ext.encode(this.store.baseParams);
321
+ }
322
+
323
+ this.postData(params);
324
+ }
325
+
326
+ },
327
+
328
+ // Handlers for tools
329
+ //
330
+
331
+ onRefresh: function() {
332
+ if (this.fireEvent('refresh', this) !== false) {
333
+ this.store.reload();
334
+ }
335
+ },
336
+
337
+ // Event handlers
338
+ //
339
+
340
+ onColumnResize: function(index, size){
341
+ this.resizeColumn({
342
+ index:index,
343
+ size:size
344
+ });
345
+ },
346
+
347
+ onColumnHiddenChange: function(cm, index, hidden){
348
+ this.hideColumn({
349
+ index:index,
350
+ hidden:hidden
351
+ });
352
+ },
353
+
354
+ onColumnMove: function(oldIndex, newIndex){
355
+ this.moveColumn({
356
+ old_index:oldIndex,
357
+ new_index:newIndex
358
+ });
359
+
360
+ var newRecordConfig = [];
361
+ Ext.each(this.getColumnModel().config, function(c){newRecordConfig.push({name: c.name})});
362
+ delete this.Row; // old record constructor
363
+ this.Row = Ext.data.Record.create(newRecordConfig);
364
+ this.getStore().reader.recordType = this.Row;
365
+ },
366
+
367
+ onRowContextMenu: function(grid, rowIndex, e){
368
+ e.stopEvent();
369
+ var coords = e.getXY();
370
+
371
+ if (!grid.getSelectionModel().isSelected(rowIndex)) {
372
+ grid.getSelectionModel().selectRow(rowIndex);
373
+ }
374
+
375
+ var menu = new Ext.menu.Menu({
376
+ items: this.contextMenu
377
+ });
378
+
379
+ menu.showAt(coords);
380
+ },
381
+
382
+ onAfterRowMove: function(dt, oldIndex, newIndex, records){
383
+ var ids = [];
384
+ // collect records ids
385
+ Ext.each(records, function(r){ids.push(r.id)});
386
+ // call GridPanel's API
387
+ this.moveRows({ids:Ext.encode(ids), new_index: newIndex});
388
+ },
389
+
390
+ // Other methods
391
+ //
392
+
393
+ loadExceptionHandler: function(proxy, options, response, error){
394
+ if (response.status == 200 && (responseObject = Ext.decode(response.responseText)) && responseObject.flash){
395
+ this.feedback(responseObject.flash);
396
+ } else {
397
+ if (error){
398
+ this.feedback(error.message);
399
+ } else {
400
+ this.feedback(response.statusText);
401
+ }
402
+ }
403
+ },
404
+
405
+ update: function(){
406
+ this.store.reload();
407
+ },
408
+
409
+ loadStoreData: function(data){
410
+ this.store.loadData(data);
411
+ Ext.each(['data', 'total', 'success'], function(property){delete data[property];}, this);
412
+ this.bulkExecute(data);
413
+ },
414
+
415
+ // try editing the first editable (i.e. not hidden, not read-only) sell
416
+ tryStartEditing: function(row){
417
+ var editableIndex = 0;
418
+ Ext.each(this.getColumnModel().config, function(c){
419
+ // skip columns that cannot be edited
420
+ if (!(c.hidden == true || c.editable == false || !c.editor || c.attrType == 'boolean')) {
421
+ return false;
422
+ }
423
+ editableIndex++;
424
+ });
425
+
426
+ if (editableIndex < this.getColumnModel().config.length) {this.startEditing(row, editableIndex);}
427
+ },
428
+
429
+ // Called by the server side to update newly created records
430
+ updateNewRecords: function(records){
431
+ this.updateRecords(records);
432
+ },
433
+
434
+ // Called by the server side to update modified records
435
+ updateModRecords: function(records){
436
+ this.updateRecords(records, true);
437
+ },
438
+
439
+ // Updates modified or newly created records, by record ID
440
+ // Example of the records argument (updated columns):
441
+ // {1098 => [1, 'value1', 'value2'], 1099 => [2, 'value1', 'value2']}
442
+ // Example of the records argument (new columns, id autogenerated by Ext):
443
+ // {"ext-record-200" => [1, 'value1', 'value2']}
444
+ updateRecords: function(records, mod){
445
+ if (!mod) {mod = false;}
446
+ var modRecordsInGrid = [].concat(this.store.getModifiedRecords()); // there must be a better way to clone an array...
447
+
448
+ // replace arrays of data in the args object with Ext.data.Record objects
449
+ for (var k in records){
450
+ records[k] = this.store.reader.readRecords([records[k]]).records[0];
451
+ }
452
+
453
+ // for each new record write the data returned by the server, and commit the record
454
+ Ext.each(modRecordsInGrid, function(recordInGrid){
455
+ if (mod ^ recordInGrid.isNew) {
456
+ // if record is new, we access its id by "id", otherwise, the id is in the primary key column
457
+ var recordId = recordInGrid.id;
458
+ // new data that the server sent us to update this record (identified by the id)
459
+ var newData = records[recordId];
460
+
461
+ if (newData){
462
+ for (var k in newData.data){
463
+ recordInGrid.set(k, newData.get(k));
464
+ }
465
+
466
+ recordInGrid.isNew = false;
467
+ recordInGrid.commit();
468
+ }
469
+
470
+ }
471
+ }, this);
472
+
473
+ // clear the selections
474
+ this.getSelectionModel().clearSelections();
475
+
476
+ // check if there are still records with errors
477
+ var modRecords = this.store.getModifiedRecords();
478
+ if (modRecords.length == 0) {
479
+ // if all records are accepted, reload the grid (so that eventual order/filtering is correct)
480
+ this.store.reload();
481
+
482
+ // ... and set default getRowClass function
483
+ this.getView().getRowClass = this.defaultGetRowClass;
484
+ } else {
485
+ this.getView().getRowClass = function(r){
486
+ return r.dirty ? "grid-dirty-record" : ""
487
+ }
488
+ }
489
+
490
+ this.getView().refresh();
491
+ this.getSelectionModel().fireEvent('selectionchange', this.getSelectionModel());
492
+ },
493
+
494
+ defaultGetRowClass: function(r){
495
+ return r.isNew ? "grid-dirty-record" : ""
496
+ },
497
+
498
+ selectFirstRow: function(){
499
+ this.getSelectionModel().suspendEvents();
500
+ this.getSelectionModel().selectRow(0);
501
+ this.getSelectionModel().resumeEvents();
502
+ },
503
+
504
+ // Normalizes the renderer for a column.
505
+ // Renderer may be:
506
+ // 1) a string that contains the name of the function to be used as renderer.
507
+ // 2) an array, where the first element is the function name, and the rest - the arguments
508
+ // that will be passed to that function along with the value to be rendered.
509
+ // The function is searched in the following objects: 1) Ext.util.Format, 2) this.
510
+ // If not found, it is simply evaluated. Handy, when as renderer we receive an inline JS function,
511
+ // or reference to a function in some other scope.
512
+ // So, these will work:
513
+ // * "uppercase"
514
+ // * ["ellipsis", 10]
515
+ // * ["substr", 3, 5]
516
+ // * "myRenderer" (if this.myRenderer is a function)
517
+ // * ["Some.scope.Format.customRenderer", 10, 20, 30] (if Some.scope.Format.customRenderer is a function)
518
+ // * "function(v){ return 'Value: ' + v; }"
519
+ normalizeRenderer: function(c) {
520
+ if (!c.renderer) return;
521
+
522
+ var name, args = [];
523
+
524
+ if ('string' === typeof c.renderer) {
525
+ name = c.renderer;
526
+ } else {
527
+ name = c.renderer[0];
528
+ args = c.renderer.slice(1);
529
+ }
530
+
531
+ // First check whether Ext.util.Format has it
532
+ if (Ext.isFunction(Ext.util.Format[name])) {
533
+ c.renderer = Ext.util.Format[name].createDelegate(this, args, 1);
534
+ } else if (Ext.isFunction(this[name])) {
535
+ // ... then if our own class has it
536
+ c.renderer = this[name].createDelegate(this, args, 1);
537
+ } else {
538
+ // ... and, as last resort, evaluate it (allows passing inline javascript function as renderer)
539
+ eval("c.renderer = " + c.renderer + ";");
540
+ }
541
+ },
542
+
543
+
544
+ onEdit: function(){
545
+ var row = this.getSelectionModel().getSelected();
546
+ if (row){
547
+ this.tryStartEditing(this.store.indexOf(row));
548
+ }
549
+ }
550
+
551
+ // :reorder_columns => <<-END_OF_JAVASCRIPT.l,
552
+ // function(columns){
553
+ // columnsInNewShipment = [];
554
+ // Ext.each(columns, function(c){
555
+ // columnsInNewShipment.push({name:c});
556
+ // });
557
+ // newRecordType = Ext.data.Record.create(columnsInNewShipment);
558
+ // 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
559
+ // }
560
+ // END_OF_JAVASCRIPT
561
+
562
+ });