netzke-basepack 0.5.12 → 0.5.13

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.
@@ -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
+ });