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.
@@ -79,13 +79,22 @@ module Netzke
79
79
  end
80
80
 
81
81
  # Returns choices for a column
82
- def get_combobox_options(params)
82
+ def get_combobox_options_DELETME(params)
83
83
  column = params[:column]
84
84
  query = params[:query]
85
85
  {:data => data_class.options_for(column, query).map{|s| [s]}}
86
86
  # {:data => data_class.options_for(column, query).map{|s| [s]}}
87
87
  end
88
88
 
89
+ # Returns choices for a column
90
+ def get_combobox_options(params)
91
+ column = columns.detect{ |c| c[:name] == params[:column] }.try(:to_options!)
92
+ scopes = (column[:editor].is_a?(Hash) && column[:editor] || {}).to_options[:scopes]
93
+ query = params[:query]
94
+
95
+ {:data => combobox_options_for_column(column, :query => query, :scopes => scopes)}
96
+ end
97
+
89
98
  def move_rows(params)
90
99
  if defined?(ActsAsList) && data_class.ancestors.include?(ActsAsList::InstanceMethods)
91
100
  ids = JSON.parse(params[:ids]).reverse
@@ -175,7 +184,7 @@ module Netzke
175
184
  normalize_extra_conditions(ActiveSupport::JSON.decode(params[:extra_conditions]))
176
185
  ) if params[:extra_conditions]
177
186
 
178
- search = data_class.search(search_params)
187
+ search = data_class.searchlogic(search_params)
179
188
 
180
189
  # applying scopes
181
190
  scopes.each do |s|
@@ -20,7 +20,7 @@ module Netzke
20
20
  {:name => "default_value", :attr_type => :string, :width => 200},
21
21
 
22
22
  # Options for drop-downs
23
- {:name => "combobox_options", :attr_type => :string, :editor => "textarea", :width => 200},
23
+ {:name => "combobox_options", :attr_type => :string, :editor => :textarea, :width => 200},
24
24
 
25
25
  # Whether the column is editable in the grid.
26
26
  {:name => "read_only", :attr_type => :boolean, :header => "R/O", :tooltip => "Read-only"},
@@ -44,74 +44,65 @@ module Netzke
44
44
 
45
45
  # Whether the column should be sortable (why change it? normally it's hardcoded)
46
46
  {:name => "sortable", :attr_type => :boolean, :default_value => true, :hidden => true},
47
-
48
- #
49
- # And finally some meta columns that we probably never want to see in the GUI
50
- #
51
- {:name => "editor", :attr_type => :string, :meta => true}
52
47
  ]
53
48
  end
54
49
 
55
50
  end
56
51
 
57
- module InstanceMethods
58
-
59
- # Normalized columns for the grid, e.g.:
60
- # [{:name => :id, :hidden => true, ...}, {:name => :name, :editable => false, ...}, ...]
61
- def columns(only_included = true)
62
- @columns ||= begin
63
- if cols = load_columns
64
- filter_out_excluded_columns(cols) if only_included
65
- cols
66
- else
67
- initial_columns(only_included)
68
- end
52
+ # Normalized columns for the grid, e.g.:
53
+ # [{:name => :id, :hidden => true, ...}, {:name => :name, :editable => false, ...}, ...]
54
+ def columns(only_included = true)
55
+ @columns ||= begin
56
+ if cols = load_columns
57
+ filter_out_excluded_columns(cols) if only_included
58
+ reverse_merge_equally_named_columns(cols, initial_columns)
59
+ cols
60
+ else
61
+ initial_columns(only_included)
69
62
  end
70
63
  end
64
+ end
65
+
66
+ # Columns that we fall back to when neither persistent columns, nor configured columns are present.
67
+ # If there's a model-level field configuration, it's being used.
68
+ # Otherwise the defaults straight from the ActiveRecord model ("netzke_attributes").
69
+ # Override this method if you want to provide a fix set of columns in your subclass.
70
+ def default_columns
71
+ @default_columns ||= load_model_level_attrs || data_class.netzke_attributes
72
+ end
73
+
74
+ # Columns that represent a smart merge of default_columns and columns passed during the configuration.
75
+ def initial_columns(only_included = true)
76
+ # Normalize here, as from the config we can get symbols (names) instead of hashes
77
+ columns_from_config = config[:columns] && normalize_attr_config(config[:columns])
71
78
 
72
- # Columns that we fall back to when neither persistent columns, nor configured columns are present.
73
- # If there's a model-level field configuration, it's being used.
74
- # Otherwise the defaults straight from the ActiveRecord model ("netzke_attributes").
75
- # Override this method if you want to provide a fix set of columns in your subclass.
76
- def default_columns
77
- @default_columns ||= load_model_level_attrs || data_class.netzke_attributes
78
- # @default_columns ||= load_model_level_attrs || data_class.netzke_attributes
79
- end
80
-
81
- # Columns that represent a smart merge of default_columns and columns passed during the configuration.
82
- def initial_columns(only_included = true)
83
- # Normalize here, as from the config we can get symbols (names) instead of hashes
84
- columns_from_config = config[:columns] && normalize_attr_config(config[:columns])
85
-
86
- if columns_from_config
87
- # reverse-merge each column hash from config with each column hash from exposed_attributes (columns from config have higher priority)
88
- for c in columns_from_config
89
- corresponding_default_column = default_columns.find{ |k| k[:name] == c[:name] }
90
- c.reverse_merge!(corresponding_default_column) if corresponding_default_column
91
- end
92
- columns_for_create = columns_from_config
93
- else
94
- # we didn't have columns configured in widget's config, so, use the columns from the data class
95
- columns_for_create = default_columns
96
- end
97
-
98
- filter_out_excluded_columns(columns_for_create) if only_included
99
-
100
- # Make the column config complete with the defaults
101
- columns_for_create.each do |c|
102
- detect_association(c)
103
- set_default_header(c)
104
- set_default_editor(c)
105
- set_default_width(c)
106
- set_default_hidden(c)
107
- set_default_editable(c)
108
- set_default_sortable(c)
109
- set_default_filterable(c)
79
+ if columns_from_config
80
+ # reverse-merge each column hash from config with each column hash from exposed_attributes (columns from config have higher priority)
81
+ for c in columns_from_config
82
+ corresponding_default_column = default_columns.find{ |k| k[:name] == c[:name] }
83
+ c.reverse_merge!(corresponding_default_column) if corresponding_default_column
110
84
  end
111
-
112
- columns_for_create
85
+ columns_for_create = columns_from_config
86
+ else
87
+ # we didn't have columns configured in widget's config, so, use the columns from the data class
88
+ columns_for_create = default_columns
113
89
  end
114
90
 
91
+ filter_out_excluded_columns(columns_for_create) if only_included
92
+
93
+ # Make the column config complete with the defaults
94
+ columns_for_create.each do |c|
95
+ detect_association(c)
96
+ set_default_header(c)
97
+ set_default_editor(c)
98
+ set_default_width(c)
99
+ set_default_hidden(c)
100
+ set_default_editable(c)
101
+ set_default_sortable(c)
102
+ set_default_filterable(c)
103
+ end
104
+
105
+ columns_for_create
115
106
  end
116
107
 
117
108
  private
@@ -121,7 +112,7 @@ module Netzke
121
112
 
122
113
  # Stores modified columns in persistent storage
123
114
  def save_columns!
124
- NetzkeFieldList.update_list_for_current_authority(global_id, columns(false), data_class.name)
115
+ NetzkeFieldList.update_list_for_current_authority(global_id, columns(false), original_data_class.name)
125
116
  end
126
117
 
127
118
  def load_columns
@@ -143,6 +134,7 @@ module Netzke
143
134
 
144
135
  def set_default_editor(c)
145
136
  c[:editor] ||= editor_for_attr_type(c[:attr_type])
137
+ c[:editor] = {:xtype => c[:editor]} if c[:editor].is_a?(Symbol)
146
138
  end
147
139
 
148
140
  def set_default_width(c)
@@ -160,11 +152,11 @@ module Netzke
160
152
  end
161
153
 
162
154
  def set_default_sortable(c)
163
- c[:sortable] = !c[:virtual]
155
+ c[:sortable] = !c[:virtual] if c[:sortable].nil?
164
156
  end
165
157
 
166
158
  def set_default_filterable(c)
167
- c[:filterable] = !c[:virtual]
159
+ c[:filterable] = !c[:virtual] if c[:filterable].nil?
168
160
  end
169
161
 
170
162
  # Returns editor's xtype for a column type
@@ -213,17 +205,39 @@ module Netzke
213
205
  assoc_method_type = assoc_column.try(:type)
214
206
 
215
207
  # if association column is boolean, display a checkbox (or alike), otherwise - a combobox (or alike)
216
- c[:editor] = assoc_method_type == :boolean ? editor_for_attr_type(:boolean) : editor_for_association
208
+ c[:editor] ||= assoc_method_type == :boolean ? editor_for_attr_type(:boolean) : editor_for_association
217
209
  end
218
210
  end
219
211
 
212
+ # Default fields that will be displayed in the Add/Edit/Search forms
220
213
  def default_fields_for_forms
221
- columns.map{ |c| {:name => c[:name]} }
214
+ form_klass = "Netzke::ModelExtensions::#{config[:model]}ForFormPanel".constantize rescue nil
215
+ form_klass ||= original_data_class
216
+
217
+ # Select only those fields that are known to the form_klass
218
+ selected_columns = columns.select do |c|
219
+ form_klass.column_names.include?(c[:name]) ||
220
+ form_klass.instance_methods.include?("#{c[:name]}=") ||
221
+ association_attr?(c[:name])
222
+ end
223
+
224
+ selected_columns.map do |c|
225
+ field_config = {:name => c[:name]}
226
+
227
+ # scopes for combobox options
228
+ field_config[:scopes] = c[:editor].is_a?(Hash) && c[:editor][:scopes]
229
+
230
+ field_config
231
+ end
222
232
  end
223
-
233
+
234
+ # Receives 2 arrays of columns. Merges the missing config from the +source+ into +dest+, matching columns by name
235
+ def reverse_merge_equally_named_columns(dest, source)
236
+ dest.each{ |dc| dc.reverse_merge!(source.detect{ |sc| sc[:name] == dc[:name] }) }
237
+ end
238
+
224
239
  def self.included(receiver)
225
240
  receiver.extend ClassMethods
226
- receiver.send :include, InstanceMethods
227
241
  end
228
242
  end
229
243
  end
@@ -18,7 +18,7 @@ module Netzke
18
18
  module ClassMethods
19
19
 
20
20
  def js_base_class
21
- 'Ext.grid.EditorGridPanel'
21
+ 'Netzke.pre.GridPanel' # see grid_panel_pre.js
22
22
  end
23
23
 
24
24
  # Ext.Component#initComponent, built up from pices (dependent on class-level configuration)
@@ -45,233 +45,8 @@ module Netzke
45
45
  # Result
46
46
  <<-END_OF_JAVASCRIPT
47
47
  function(){
48
- if (!this.clmns) {this.feedback('No columns defined for grid '+this.id);}
49
-
50
- /* Process columns - all in sake of creating the column model */
51
- // Normalize columns passed in the config
52
- var normClmns = [];
53
- Ext.each(this.clmns, function(c){
54
- // normalize columns
55
- if (typeof c == 'string') {
56
- normClmns.push({name:c});
57
- } else {
58
- normClmns.push(c);
59
- }
60
- });
61
-
62
- delete this.clmns; // we don't need them anymore
63
-
64
- var cmConfig = []; // column model config - we'll use it later to create the ColumnModel
65
- this.plugins = []; // checkbox colums is a special case, being a plugin
66
-
67
- var filters = [];
68
-
69
- // Run through columns and set up different configuration for each
70
- Ext.each(normClmns, function(c){
71
- // We will not use meta columns as actual columns (not even hidden) - only to create the records
72
- if (c.meta) return;
73
-
74
- // Apply default column config
75
- Ext.applyIf(c, this.defaultColumnConfig);
76
-
77
- // setting dataIndex separately
78
- c.dataIndex = c.name;
79
-
80
- // Automatically calculated default values
81
- if (!c.header) {c.header = c.label || c.name.humanize()}
82
-
83
- // normalize editor
84
- if (c.editor) {
85
- c.editor = Netzke.isObject(c.editor) ? c.editor : {xtype:c.editor};
86
- } else {
87
- c.editor = {xtype: this.attrTypeEditorMap[c.attrType] || 'textfield'}
88
- }
89
-
90
- // if comboboxOptions are provided, we render a combobox instead of textfield
91
- if (c.comboboxOptions && c.editor.xtype === "textfield") {
92
- c.editor = {xtype: "combobox", options: c.comboboxOptions.split('\\n')}
93
- }
94
-
95
- // collect filters
96
- if (c.filterable){
97
- filters.push({type:this.filterTypeForAttrType(c.attrType), dataIndex:c.name});
98
- }
99
-
100
- if (c.editor && c.editor.xtype == 'checkbox') {
101
- // Special case of checkbox column
102
- var plugin = new Ext.ux.grid.CheckColumn(c);
103
- this.plugins.push(plugin);
104
- cmConfig.push(plugin);
105
- } else {
106
- // a "normal" column, not a plugin
107
- if (!c.readOnly && !this.prohibitUpdate) {
108
- // c.editor contains complete config of the editor
109
- var xtype = c.editor.xtype;
110
- c.editor = Ext.ComponentMgr.create(Ext.apply({
111
- parentId: this.id,
112
- name: c.name,
113
- selectOnFocus:true
114
- }, c.editor));
115
- } else {
116
- c.editor = null;
117
- }
118
-
119
- // Normalize the renderer
120
- this.normalizeRenderer(c);
121
-
122
- // set the renderer
123
- // if (c.renderer && !Ext.isArray(c.renderer) && c.renderer.match(/^\\s*function\\s*\\(/)) {
124
- // // if the renderer is an inline function - eval it (double escaping because we are inside of the Ruby string here...)
125
- // eval("c.renderer = " + c.renderer + ";");
126
- // } else if (Ext.isFunction(this[c.renderer])) {
127
- // // whether the renderer is defined in this.scope
128
- // c.renderer = this[c.renderer].createDelegate(this);
129
- // } else {
130
- // // othrewise it's a string representing the name of the renderer or a json-encoded array,
131
- // // where the first parameter is the renderer's name, and the rest - parameters that should be
132
- // // passed to the renderer at the moment of calling
133
- // var renderer = Ext.netzke.normalizedRenderer(c.renderer);
134
- // if (renderer != null) {
135
- // c.renderer = renderer
136
- // };
137
- // }
138
- //
139
- // add to the list
140
- cmConfig.push(c);
141
- }
142
-
143
- }, this);
144
-
145
- // Finally, create the ColumnModel based on processed columns
146
- this.cm = new Ext.grid.ColumnModel(cmConfig);
147
-
148
- // Hidden change event
149
- if (this.persistentConfig) {this.cm.on('hiddenchange', this.onColumnHiddenChange, this);}
150
-
151
- /* ... and done with columns */
152
-
153
- // Filters
154
- if (this.enableColumnFilters) {
155
- this.plugins.push(new Ext.ux.grid.GridFilters({filters:filters}));
156
- }
157
-
158
- // Create Ext.data.Record constructor specific for our particular column configuration
159
- this.recordConfig = [];
160
- Ext.each(normClmns, function(column){this.recordConfig.push({name:column.name, defaultValue:column.defaultValue});}, this);
161
- this.Row = Ext.data.Record.create(this.recordConfig);
162
-
163
- // Drag'n'Drop
164
- if (this.enableRowsReordering){
165
- this.ddPlugin = new Ext.ux.dd.GridDragDropRowOrder({
166
- scrollable: true // enable scrolling support (default is false)
167
- });
168
- this.plugins.push(this.ddPlugin);
169
- }
170
-
171
- // Explicitely create the connection to get grid's data,
172
- // because we don't want the app-wide Ext.Ajax to be used,
173
- // as we are going to subscribe to its events
174
- var connection = new Ext.data.Connection({
175
- url: this.buildApiUrl("get_data"),
176
- extraParams: {
177
- authenticity_token : Netzke.authenticityToken
178
- },
179
-
180
- // inform Ext.Ajax about our events
181
- listeners: {
182
- beforerequest: function(){
183
- Ext.Ajax.fireEvent('beforerequest', arguments);
184
- },
185
- requestexception: function(){
186
- Ext.Ajax.fireEvent('requestexception', arguments);
187
- },
188
- requestcomplete: function(){
189
- Ext.Ajax.fireEvent('requestcomplete', arguments);
190
- }
191
- }
192
- });
193
-
194
- // besides getting data into the store, we may also get commands to execute
195
- connection.on('requestcomplete', function(conn, r){
196
- var response = Ext.decode(r.responseText);
197
-
198
- // delete data-related properties
199
- Ext.each(['data', 'total', 'success'], function(property){delete response[property];});
200
- this.bulkExecute(response);
201
- }, this);
202
-
203
- // HttpProxy that uses our custom connection
204
- var httpProxy = new Ext.data.HttpProxy(connection);
205
-
206
- // Data store
207
- this.store = new Ext.data.Store({
208
- proxy: this.proxy = httpProxy,
209
- reader: new Ext.data.ArrayReader({root: "data", totalProperty: "total", successProperty: "success", id:0}, this.Row),
210
- remoteSort: true,
211
- listeners:{'loadexception':{
212
- fn:this.loadExceptionHandler,
213
- scope:this
214
- }}
215
- });
216
-
217
- // Normalize bottom bar
218
- this.bbar = (this.enablePagination) ? new Ext.PagingToolbar({
219
- pageSize : this.rowsPerPage,
220
- items : this.bbar ? ["-"].concat(this.bbar) : [],
221
- store : this.store,
222
- emptyMsg: 'Empty',
223
- displayInfo: true
224
- }) : this.bbar;
225
-
226
- // Selection model
227
- this.sm = new Ext.grid.RowSelectionModel();
228
-
229
- // Now let Ext.grid.EditorGridPanel do the rest
230
48
  // Original initComponent
231
49
  #{js_full_class_name}.superclass.initComponent.call(this);
232
-
233
- // Inform the server part about column operations
234
- if (this.persistentConfig) {
235
- this.on('columnresize', this.onColumnResize, this);
236
- this.on('columnmove', this.onColumnMove, this);
237
- }
238
-
239
- // Context menu
240
- if (this.enableContextMenu) {
241
- this.on('rowcontextmenu', this.onRowContextMenu, this);
242
- }
243
-
244
- // Load data AFTER the toolbar is bound to the store, which will provide for correct page number
245
- if (this.loadInlineData) {
246
- this.getStore().loadData(this.inlineData);
247
-
248
- // If rows per page specified, fake store.lastOptions as if the data was loaded
249
- // by PagingToolbar (for correct functionning of refresh tool and extended search)
250
- if (this.rowsPerPage) {
251
- this.getStore().lastOptions = {params:{limit:this.rowsPerPage, start:0}}; // this is how PagingToolbar does it...
252
- }
253
-
254
- // inlineData may also contain commands (TODO: make it DRY)
255
- // delete data-related properties
256
- Ext.each(['data', 'total', 'success'], function(property){delete this.inlineData[property];}, this);
257
- this.bulkExecute(this.inlineData);
258
- }
259
-
260
- // Process selectionchange event
261
- this.getSelectionModel().on('selectionchange', function(selModel){
262
- // enable/disable actions
263
- this.actions.del.setDisabled(!selModel.hasSelection() || this.prohibitDelete);
264
- this.actions.edit.setDisabled(selModel.getCount() != 1 || this.prohibitUpdate);
265
- }, this);
266
-
267
- // Drag n Drop event
268
- if (this.enableRowsReordering){
269
- this.ddPlugin.on('afterrowmove', this.onAfterRowMove, this);
270
- }
271
-
272
- // GridView
273
- this.getView().getRowClass = this.defaultGetRowClass;
274
-
275
50
  #{edit_in_form_events}
276
51
  }
277
52
 
@@ -281,518 +56,9 @@ module Netzke
281
56
 
282
57
  # All the rest that makes our JavaScript class
283
58
  def js_extend_properties
284
- res = super
285
-
286
- res.merge!(
287
- {
288
- # all the options are overrideable in config, of course
289
- :track_mouse_over => true,
290
- :load_mask => true,
291
- :auto_scroll => true,
292
-
293
- :default_column_config => meta_columns.inject({}){ |r, c| c.is_a?(Hash) ? r.merge(c[:name] => c[:default]) : r },
294
-
295
- :init_component => js_init_component.l,
296
-
297
- :attr_type_editor_map => {
298
- :integer => "numberfield",
299
- :boolean => "checkbox",
300
- :decimal => "numberfield",
301
- :datetime => "xdatetime",
302
- :date => "datefield",
303
- :string => "textfield"
304
- },
305
-
306
- :filter_type_for_attr_type => <<-END_OF_JAVASCRIPT.l,
307
- function(attrType){
308
- var map = {
309
- integer :'Numeric',
310
- decimal :'Numeric',
311
- datetime:'Date',
312
- date :'Date',
313
- string :'String'
314
- };
315
- map['boolean'] = "Boolean"; // "boolean" is a JS reserved word
316
- return map[attrType] || 'String';
317
- }
318
- END_OF_JAVASCRIPT
319
-
320
- # Handlers for actions
321
- #
322
-
323
- :on_add => <<-END_OF_JAVASCRIPT.l,
324
- function(){
325
- var r = new this.Row();
326
- r.isNew = true; // to distinguish new records
327
- // r.set('id', r.id); // otherwise later r.get('id') returns empty string
328
- this.stopEditing();
329
- this.getStore().add(r);
330
-
331
- // Set default values
332
- this.getStore().fields.each(function(field){
333
- r.set(field.name, field.defaultValue);
334
- });
335
-
336
- this.tryStartEditing(this.store.indexOf(r));
337
- }
338
- END_OF_JAVASCRIPT
339
-
340
- :on_edit => <<-END_OF_JAVASCRIPT.l,
341
- function(){
342
- var row = this.getSelectionModel().getSelected();
343
- if (row){
344
- this.tryStartEditing(this.store.indexOf(row));
345
- }
346
- }
347
- END_OF_JAVASCRIPT
348
-
349
- :on_del => <<-END_OF_JAVASCRIPT.l,
350
- function() {
351
- Ext.Msg.confirm('Confirm', 'Are you sure?', function(btn){
352
- if (btn == 'yes') {
353
- var records = [];
354
- this.getSelectionModel().each(function(r){
355
- if (r.isNew) {
356
- // this record is not know to server - simply remove from store
357
- this.store.remove(r);
358
- } else {
359
- records.push(r.id);
360
- }
361
- }, this);
362
-
363
- if (records.length > 0){
364
- // call API
365
- this.deleteData({records: Ext.encode(records)});
366
- }
367
- }
368
- }, this);
369
- }
370
- END_OF_JAVASCRIPT
371
-
372
- :on_apply => <<-END_OF_JAVASCRIPT.l,
373
- function(){
374
- var newRecords = [];
375
- var updatedRecords = [];
376
- Ext.each(this.store.getModifiedRecords(),
377
- function(r) {
378
- if (r.isNew) {
379
- newRecords.push(Ext.apply(r.getChanges(), {id:r.id}));
380
- } else {
381
- updatedRecords.push(Ext.apply(r.getChanges(), {id:r.id}));
382
- }
383
- },
384
- this);
385
-
386
- if (newRecords.length > 0 || updatedRecords.length > 0) {
387
- var params = {};
388
-
389
- if (newRecords.length > 0) {
390
- params.created_records = Ext.encode(newRecords);
391
- }
392
-
393
- if (updatedRecords.length > 0) {
394
- params.updated_records = Ext.encode(updatedRecords);
395
- }
396
-
397
- if (this.store.baseParams !== {}) {
398
- params.base_params = Ext.encode(this.store.baseParams);
399
- }
400
-
401
- this.postData(params);
402
- }
403
-
404
- }
405
- END_OF_JAVASCRIPT
406
-
407
- # Handlers for tools
408
- #
409
-
410
- :on_refresh => <<-END_OF_JAVASCRIPT.l,
411
- function() {
412
- if (this.fireEvent('refresh', this) !== false) {
413
- this.store.reload();
414
- }
415
- }
416
- END_OF_JAVASCRIPT
417
-
418
- # Event handlers
419
- #
420
-
421
- :on_column_resize => <<-END_OF_JAVASCRIPT.l,
422
- function(index, size){
423
- this.resizeColumn({
424
- index:index,
425
- size:size
426
- });
427
- }
428
- END_OF_JAVASCRIPT
429
-
430
- :on_column_hidden_change => <<-END_OF_JAVASCRIPT.l,
431
- function(cm, index, hidden){
432
- this.hideColumn({
433
- index:index,
434
- hidden:hidden
435
- });
436
- }
437
- END_OF_JAVASCRIPT
438
-
439
- :on_column_move => <<-END_OF_JAVASCRIPT.l,
440
- function(oldIndex, newIndex){
441
- this.moveColumn({
442
- old_index:oldIndex,
443
- new_index:newIndex
444
- });
445
-
446
- var newRecordConfig = [];
447
- Ext.each(this.getColumnModel().config, function(c){newRecordConfig.push({name: c.name})});
448
- delete this.Row; // old record constructor
449
- this.Row = Ext.data.Record.create(newRecordConfig);
450
- this.getStore().reader.recordType = this.Row;
451
- }
452
- END_OF_JAVASCRIPT
453
-
454
- :on_row_context_menu => <<-END_OF_JAVASCRIPT.l,
455
- function(grid, rowIndex, e){
456
- e.stopEvent();
457
- var coords = e.getXY();
458
-
459
- if (!grid.getSelectionModel().isSelected(rowIndex)) {
460
- grid.getSelectionModel().selectRow(rowIndex);
461
- }
462
-
463
- var menu = new Ext.menu.Menu({
464
- items: this.contextMenu
465
- });
466
-
467
- menu.showAt(coords);
468
- }
469
- END_OF_JAVASCRIPT
470
-
471
- :on_after_row_move => <<-END_OF_JAVASCRIPT.l,
472
- function(dt, oldIndex, newIndex, records){
473
- var ids = [];
474
- // collect records ids
475
- Ext.each(records, function(r){ids.push(r.id)});
476
- // call GridPanel's API
477
- this.moveRows({ids:Ext.encode(ids), new_index: newIndex});
478
- }
479
- END_OF_JAVASCRIPT
480
-
481
- # Other methods
482
- #
483
-
484
- :load_exception_handler => <<-END_OF_JAVASCRIPT.l,
485
- function(proxy, options, response, error){
486
- if (response.status == 200 && (responseObject = Ext.decode(response.responseText)) && responseObject.flash){
487
- this.feedback(responseObject.flash);
488
- } else {
489
- if (error){
490
- this.feedback(error.message);
491
- } else {
492
- this.feedback(response.statusText);
493
- }
494
- }
495
- }
496
- END_OF_JAVASCRIPT
497
-
498
- :update => <<-END_OF_JAVASCRIPT.l,
499
- function(){
500
- this.store.reload();
501
- }
502
- END_OF_JAVASCRIPT
503
-
504
- :load_store_data => <<-END_OF_JAVASCRIPT.l,
505
- function(data){
506
- this.store.loadData(data);
507
- Ext.each(['data', 'total', 'success'], function(property){delete data[property];}, this);
508
- this.bulkExecute(data);
509
- }
510
- END_OF_JAVASCRIPT
511
-
512
- # try editing the first editable (i.e. not hidden, not read-only) sell
513
- :try_start_editing => <<-END_OF_JAVASCRIPT.l,
514
- function(row){
515
- var editableIndex = 0;
516
- Ext.each(this.getColumnModel().config, function(c){
517
- // skip columns that cannot be edited
518
- if (!(c.hidden == true || c.editable == false || !c.editor || c.attrType == 'boolean')) {
519
- return false;
520
- }
521
- editableIndex++;
522
- });
523
-
524
- if (editableIndex < this.getColumnModel().config.length) {this.startEditing(row, editableIndex);}
525
- }
526
- END_OF_JAVASCRIPT
527
-
528
- # Called by the server side to update newly created records
529
- :update_new_records => <<-END_OF_JAVASCRIPT.l,
530
- function(records){
531
- this.updateRecords(records);
532
- }
533
- END_OF_JAVASCRIPT
534
-
535
- # Called by the server side to update modified records
536
- :update_mod_records => <<-END_OF_JAVASCRIPT.l,
537
- function(records){
538
- this.updateRecords(records, true);
539
- }
540
- END_OF_JAVASCRIPT
541
-
542
- # Updates modified or newly created records, by record ID
543
- # Example of the records argument (updated columns):
544
- # {1098 => [1, 'value1', 'value2'], 1099 => [2, 'value1', 'value2']}
545
- # Example of the records argument (new columns, id autogenerated by Ext):
546
- # {"ext-record-200" => [1, 'value1', 'value2']}
547
- :update_records => <<-END_OF_JAVASCRIPT.l,
548
- function(records, mod){
549
- if (!mod) {mod = false;}
550
- var modRecordsInGrid = [].concat(this.store.getModifiedRecords()); // there must be a better way to clone an array...
551
-
552
- // replace arrays of data in the args object with Ext.data.Record objects
553
- for (var k in records){
554
- records[k] = this.store.reader.readRecords([records[k]]).records[0];
555
- }
556
-
557
- // for each new record write the data returned by the server, and commit the record
558
- Ext.each(modRecordsInGrid, function(recordInGrid){
559
- if (mod ^ recordInGrid.isNew) {
560
- // if record is new, we access its id by "id", otherwise, the id is in the primary key column
561
- var recordId = recordInGrid.id;
562
- // new data that the server sent us to update this record (identified by the id)
563
- var newData = records[recordId];
564
-
565
- if (newData){
566
- for (var k in newData.data){
567
- recordInGrid.set(k, newData.get(k));
568
- }
569
-
570
- recordInGrid.isNew = false;
571
- recordInGrid.commit();
572
- }
573
-
574
- }
575
- }, this);
576
-
577
- // clear the selections
578
- this.getSelectionModel().clearSelections();
579
-
580
- // check if there are still records with errors
581
- var modRecords = this.store.getModifiedRecords();
582
- if (modRecords.length == 0) {
583
- // if all records are accepted, reload the grid (so that eventual order/filtering is correct)
584
- this.store.reload();
585
-
586
- // ... and set default getRowClass function
587
- this.getView().getRowClass = this.defaultGetRowClass;
588
- } else {
589
- this.getView().getRowClass = function(r){
590
- return r.dirty ? "grid-dirty-record" : ""
591
- }
592
- }
593
-
594
- this.getView().refresh();
595
- this.getSelectionModel().fireEvent('selectionchange', this.getSelectionModel());
596
- }
597
- END_OF_JAVASCRIPT
598
-
599
- :default_get_row_class => <<-END_OF_JAVASCRIPT.l,
600
- function(r){
601
- return r.isNew ? "grid-dirty-record" : ""
602
- }
603
- END_OF_JAVASCRIPT
604
-
605
- :select_first_row => <<-END_OF_JAVASCRIPT.l,
606
- function(){
607
- this.getSelectionModel().suspendEvents();
608
- this.getSelectionModel().selectRow(0);
609
- this.getSelectionModel().resumeEvents();
610
- }
611
- END_OF_JAVASCRIPT
612
-
613
- # Normalizes the renderer for a column.
614
- # Renderer may be:
615
- # 1) a string that contains the name of the function to be used as renderer.
616
- # 2) an array, where the first element is the function name, and the rest - the arguments
617
- # that will be passed to that function along with the value to be rendered.
618
- # The function is searched in the following objects: 1) Ext.util.Format, 2) this.
619
- # If not found, it is simply evaluated. Handy, when as renderer we receive an inline JS function,
620
- # or reference to a function in some other scope.
621
- # So, these will work:
622
- # * "uppercase"
623
- # * ["ellipsis", 10]
624
- # * ["substr", 3, 5]
625
- # * "myRenderer" (if this.myRenderer is a function)
626
- # * ["Some.scope.Format.customRenderer", 10, 20, 30] (if Some.scope.Format.customRenderer is a function)
627
- # * "function(v){ return 'Value: ' + v; }"
628
- :normalize_renderer => <<-END_OF_JAVASCRIPT.l,
629
- function(c) {
630
- if (!c.renderer) return;
631
-
632
- var name, args = [];
633
-
634
- if ('string' === typeof c.renderer) {
635
- name = c.renderer;
636
- } else {
637
- name = c.renderer[0];
638
- args = c.renderer.slice(1);
639
- }
640
-
641
- // First check whether Ext.util.Format has it
642
- if (Ext.isFunction(Ext.util.Format[name])) {
643
- c.renderer = Ext.util.Format[name].createDelegate(this, args, 1);
644
- } else if (Ext.isFunction(this[name])) {
645
- // ... then if our own class has it
646
- c.renderer = this[name].createDelegate(this, args, 1);
647
- } else {
648
- // ... and, as last resort, evaluate it (allows passing inline javascript function as renderer)
649
- eval("c.renderer = " + c.renderer + ";");
650
- }
651
- }
652
- END_OF_JAVASCRIPT
653
-
654
-
655
- # :reorder_columns => <<-END_OF_JAVASCRIPT.l,
656
- # function(columns){
657
- # columnsInNewShipment = [];
658
- # Ext.each(columns, function(c){
659
- # columnsInNewShipment.push({name:c});
660
- # });
661
- # newRecordType = Ext.data.Record.create(columnsInNewShipment);
662
- # 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
663
- # }
664
- # END_OF_JAVASCRIPT
665
- }
666
- )
667
-
668
- # Optional edit in form functionality
669
- res.merge!(
670
59
  {
671
- :on_edit_in_form => <<-END_OF_JAVASCRIPT.l,
672
- function(){
673
- var selModel = this.getSelectionModel();
674
- if (selModel.getCount() > 1) {
675
- var recordId = selModel.getSelected().id;
676
- this.loadAggregatee({id: "multiEditForm",
677
- params: {record_id: recordId},
678
- callback: function(w){
679
- var form = w.items.first();
680
- form.on('apply', function(){
681
- var ids = [];
682
- selModel.each(function(r){
683
- ids.push(r.id);
684
- });
685
- form.baseParams = {ids: Ext.encode(ids)}
686
- }, this);
687
-
688
- w.on('close', function(){
689
- if (w.closeRes === "ok") {
690
- this.store.reload();
691
- }
692
- }, this);
693
- }, scope: this});
694
- } else {
695
- var recordId = selModel.getSelected().id;
696
- this.loadAggregatee({id: "editForm",
697
- params: {record_id: recordId},
698
- callback: function(form){
699
- form.on('close', function(){
700
- if (form.closeRes === "ok") {
701
- this.store.reload();
702
- }
703
- }, this);
704
- }, scope: this});
705
- }
706
- }
707
- END_OF_JAVASCRIPT
708
-
709
- :on_add_in_form => <<-END_OF_JAVASCRIPT.l,
710
- function(){
711
- this.loadAggregatee({id: "addForm", callback: function(form){
712
- form.on('close', function(){
713
- if (form.closeRes === "ok") {
714
- this.store.reload();
715
- }
716
- }, this);
717
- }, scope: this});
718
- }
719
- END_OF_JAVASCRIPT
60
+ :init_component => js_init_component.l
720
61
  }
721
- ) if config[:edit_in_form_available]
722
-
723
- # Optional extended search functionality
724
- res.merge!(
725
- {
726
- :on_search => <<-END_OF_JAVASCRIPT.l,
727
- function(){
728
- delete this.searchWindow;
729
- this.searchWindow = new Ext.Window({
730
- title:'Advanced search',
731
- layout:'fit',
732
- modal: true,
733
- width: 400,
734
- height: Ext.lib.Dom.getViewHeight() *0.9,
735
- closeAction:'close',
736
- buttons:[{
737
- text: 'OK',
738
- handler: function(){
739
- this.ownerCt.ownerCt.closePositively();
740
- }
741
- },{
742
- text:'Cancel',
743
- handler:function(){
744
- this.ownerCt.ownerCt.closeNegatively();
745
- }
746
- }],
747
- closePositively : function(){
748
- this.conditions = this.getWidget().getForm().getValues();
749
- this.closeRes = 'OK';
750
- this.close();
751
- },
752
- closeNegatively: function(){
753
- this.closeRes = 'cancel';
754
- this.close();
755
- }
756
- });
757
-
758
- this.searchWindow.on('close', function(){
759
- if (this.searchWindow.closeRes == 'OK'){
760
- var searchConditions = this.searchWindow.conditions;
761
- var filtered = false;
762
- // check if there's any search condition set
763
- for (var k in searchConditions) {
764
- if (searchConditions[k].length > 0) {
765
- filtered = true;
766
- break;
767
- }
768
- }
769
- this.actions.search.setText(filtered ? "Search *" : "Search");
770
- this.getStore().baseParams = {extra_conditions: Ext.encode(this.searchWindow.conditions)};
771
- this.getStore().load();
772
- }
773
- }, this);
774
-
775
- this.searchWindow.on('add', function(container, searchPanel){
776
- searchPanel.on('apply', function(widget){
777
- this.searchWindow.closePositively();
778
- return false; // stop the event
779
- }, this);
780
- }, this);
781
-
782
- this.searchWindow.show(null, function(){
783
- this.searchWindow.closeRes = 'cancel';
784
- if (!this.searchWindow.getWidget()){
785
- this.loadAggregatee({id:"searchPanel", container:this.searchWindow.id});
786
- }
787
- }, this);
788
-
789
- }
790
- END_OF_JAVASCRIPT
791
-
792
- }
793
- ) if config[:extended_search_available]
794
-
795
- res
796
62
  end
797
63
  end
798
64