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.
@@ -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